@workflow/web-shared 4.1.0-beta.52 → 4.1.0-beta.54

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 (145) hide show
  1. package/dist/components/error-boundary.d.ts +15 -20
  2. package/dist/components/error-boundary.d.ts.map +1 -1
  3. package/dist/components/error-boundary.js +17 -31
  4. package/dist/components/error-boundary.js.map +1 -1
  5. package/dist/components/event-list-view.d.ts +7 -6
  6. package/dist/components/event-list-view.d.ts.map +1 -1
  7. package/dist/components/event-list-view.js +492 -109
  8. package/dist/components/event-list-view.js.map +1 -1
  9. package/dist/components/index.d.ts +1 -0
  10. package/dist/components/index.d.ts.map +1 -1
  11. package/dist/components/index.js +1 -0
  12. package/dist/components/index.js.map +1 -1
  13. package/dist/components/run-trace-view.d.ts +2 -1
  14. package/dist/components/run-trace-view.d.ts.map +1 -1
  15. package/dist/components/run-trace-view.js +2 -2
  16. package/dist/components/run-trace-view.js.map +1 -1
  17. package/dist/components/sidebar/attribute-panel.d.ts +2 -1
  18. package/dist/components/sidebar/attribute-panel.d.ts.map +1 -1
  19. package/dist/components/sidebar/attribute-panel.js +53 -142
  20. package/dist/components/sidebar/attribute-panel.js.map +1 -1
  21. package/dist/components/sidebar/conversation-view.d.ts.map +1 -1
  22. package/dist/components/sidebar/conversation-view.js +3 -17
  23. package/dist/components/sidebar/conversation-view.js.map +1 -1
  24. package/dist/components/sidebar/entity-detail-panel.d.ts +3 -1
  25. package/dist/components/sidebar/entity-detail-panel.d.ts.map +1 -1
  26. package/dist/components/sidebar/entity-detail-panel.js +63 -10
  27. package/dist/components/sidebar/entity-detail-panel.js.map +1 -1
  28. package/dist/components/sidebar/events-list.d.ts.map +1 -1
  29. package/dist/components/sidebar/events-list.js +4 -8
  30. package/dist/components/sidebar/events-list.js.map +1 -1
  31. package/dist/components/sidebar/resolve-hook-modal.d.ts +3 -0
  32. package/dist/components/sidebar/resolve-hook-modal.d.ts.map +1 -1
  33. package/dist/components/sidebar/resolve-hook-modal.js +152 -3
  34. package/dist/components/sidebar/resolve-hook-modal.js.map +1 -1
  35. package/dist/components/stream-viewer.d.ts +7 -5
  36. package/dist/components/stream-viewer.d.ts.map +1 -1
  37. package/dist/components/stream-viewer.js +54 -22
  38. package/dist/components/stream-viewer.js.map +1 -1
  39. package/dist/components/trace-viewer/components/markers.d.ts +2 -1
  40. package/dist/components/trace-viewer/components/markers.d.ts.map +1 -1
  41. package/dist/components/trace-viewer/components/markers.js +59 -20
  42. package/dist/components/trace-viewer/components/markers.js.map +1 -1
  43. package/dist/components/trace-viewer/components/node.d.ts +5 -1
  44. package/dist/components/trace-viewer/components/node.d.ts.map +1 -1
  45. package/dist/components/trace-viewer/components/node.js +250 -68
  46. package/dist/components/trace-viewer/components/node.js.map +1 -1
  47. package/dist/components/trace-viewer/components/span-content.d.ts +19 -0
  48. package/dist/components/trace-viewer/components/span-content.d.ts.map +1 -0
  49. package/dist/components/trace-viewer/components/span-content.js +137 -0
  50. package/dist/components/trace-viewer/components/span-content.js.map +1 -0
  51. package/dist/components/trace-viewer/components/span-detail-panel.d.ts.map +1 -1
  52. package/dist/components/trace-viewer/components/span-detail-panel.js +3 -2
  53. package/dist/components/trace-viewer/components/span-detail-panel.js.map +1 -1
  54. package/dist/components/trace-viewer/components/span-segments.d.ts +50 -0
  55. package/dist/components/trace-viewer/components/span-segments.d.ts.map +1 -0
  56. package/dist/components/trace-viewer/components/span-segments.js +392 -0
  57. package/dist/components/trace-viewer/components/span-segments.js.map +1 -0
  58. package/dist/components/trace-viewer/components/span-strategies.d.ts +46 -0
  59. package/dist/components/trace-viewer/components/span-strategies.d.ts.map +1 -0
  60. package/dist/components/trace-viewer/components/span-strategies.js +108 -0
  61. package/dist/components/trace-viewer/components/span-strategies.js.map +1 -0
  62. package/dist/components/trace-viewer/context.d.ts +7 -6
  63. package/dist/components/trace-viewer/context.d.ts.map +1 -1
  64. package/dist/components/trace-viewer/context.js +47 -18
  65. package/dist/components/trace-viewer/context.js.map +1 -1
  66. package/dist/components/trace-viewer/trace-viewer.d.ts +5 -1
  67. package/dist/components/trace-viewer/trace-viewer.d.ts.map +1 -1
  68. package/dist/components/trace-viewer/trace-viewer.js +87 -11
  69. package/dist/components/trace-viewer/trace-viewer.js.map +1 -1
  70. package/dist/components/trace-viewer/trace-viewer.module.css +179 -6
  71. package/dist/components/trace-viewer/util/timing.d.ts +5 -0
  72. package/dist/components/trace-viewer/util/timing.d.ts.map +1 -1
  73. package/dist/components/trace-viewer/util/timing.js +12 -0
  74. package/dist/components/trace-viewer/util/timing.js.map +1 -1
  75. package/dist/components/trace-viewer/util/use-streaming-spans.d.ts +1 -1
  76. package/dist/components/trace-viewer/util/use-streaming-spans.d.ts.map +1 -1
  77. package/dist/components/trace-viewer/util/use-streaming-spans.js +29 -17
  78. package/dist/components/trace-viewer/util/use-streaming-spans.js.map +1 -1
  79. package/dist/components/trace-viewer/worker.js +3 -1
  80. package/dist/components/trace-viewer/worker.js.map +1 -1
  81. package/dist/components/ui/alert.js +3 -3
  82. package/dist/components/ui/alert.js.map +1 -1
  83. package/dist/components/ui/card.d.ts.map +1 -1
  84. package/dist/components/ui/card.js +2 -2
  85. package/dist/components/ui/card.js.map +1 -1
  86. package/dist/components/ui/data-inspector.d.ts +17 -0
  87. package/dist/components/ui/data-inspector.d.ts.map +1 -0
  88. package/dist/components/ui/data-inspector.js +184 -0
  89. package/dist/components/ui/data-inspector.js.map +1 -0
  90. package/dist/components/ui/error-card.d.ts.map +1 -1
  91. package/dist/components/ui/error-card.js +4 -1
  92. package/dist/components/ui/error-card.js.map +1 -1
  93. package/dist/components/ui/inspector-theme.d.ts +39 -24
  94. package/dist/components/ui/inspector-theme.d.ts.map +1 -1
  95. package/dist/components/ui/inspector-theme.js +90 -38
  96. package/dist/components/ui/inspector-theme.js.map +1 -1
  97. package/dist/components/ui/skeleton.d.ts +1 -1
  98. package/dist/components/ui/skeleton.d.ts.map +1 -1
  99. package/dist/components/ui/skeleton.js +2 -2
  100. package/dist/components/ui/skeleton.js.map +1 -1
  101. package/dist/components/workflow-trace-view.d.ts +3 -1
  102. package/dist/components/workflow-trace-view.d.ts.map +1 -1
  103. package/dist/components/workflow-trace-view.js +435 -21
  104. package/dist/components/workflow-trace-view.js.map +1 -1
  105. package/dist/components/workflow-traces/trace-span-construction.d.ts +1 -1
  106. package/dist/components/workflow-traces/trace-span-construction.d.ts.map +1 -1
  107. package/dist/components/workflow-traces/trace-span-construction.js +2 -2
  108. package/dist/components/workflow-traces/trace-span-construction.js.map +1 -1
  109. package/dist/lib/hydration.d.ts.map +1 -1
  110. package/dist/lib/hydration.js +17 -3
  111. package/dist/lib/hydration.js.map +1 -1
  112. package/dist/styles.css +186 -0
  113. package/package.json +8 -7
  114. package/src/components/error-boundary.tsx +29 -40
  115. package/src/components/event-list-view.tsx +1000 -287
  116. package/src/components/index.ts +1 -0
  117. package/src/components/run-trace-view.tsx +3 -0
  118. package/src/components/sidebar/attribute-panel.tsx +58 -258
  119. package/src/components/sidebar/conversation-view.tsx +30 -27
  120. package/src/components/sidebar/entity-detail-panel.tsx +86 -20
  121. package/src/components/sidebar/events-list.tsx +4 -11
  122. package/src/components/sidebar/resolve-hook-modal.tsx +206 -47
  123. package/src/components/stream-viewer.tsx +138 -61
  124. package/src/components/trace-viewer/components/markers.tsx +69 -21
  125. package/src/components/trace-viewer/components/node.tsx +346 -100
  126. package/src/components/trace-viewer/components/span-content.tsx +247 -0
  127. package/src/components/trace-viewer/components/span-detail-panel.tsx +7 -2
  128. package/src/components/trace-viewer/components/span-segments.ts +516 -0
  129. package/src/components/trace-viewer/components/span-strategies.ts +205 -0
  130. package/src/components/trace-viewer/context.tsx +92 -40
  131. package/src/components/trace-viewer/trace-viewer.module.css +179 -6
  132. package/src/components/trace-viewer/trace-viewer.tsx +115 -11
  133. package/src/components/trace-viewer/util/timing.ts +13 -0
  134. package/src/components/trace-viewer/util/use-streaming-spans.ts +28 -17
  135. package/src/components/trace-viewer/worker.ts +4 -1
  136. package/src/components/ui/alert.tsx +3 -3
  137. package/src/components/ui/card.tsx +3 -5
  138. package/src/components/ui/data-inspector.tsx +318 -0
  139. package/src/components/ui/error-card.tsx +17 -6
  140. package/src/components/ui/inspector-theme.ts +127 -39
  141. package/src/components/ui/skeleton.tsx +3 -1
  142. package/src/components/workflow-trace-view.tsx +625 -26
  143. package/src/components/workflow-traces/trace-span-construction.ts +3 -2
  144. package/src/lib/hydration.ts +17 -8
  145. package/src/styles.css +186 -0
@@ -53,6 +53,7 @@ export interface SelectedSpanInfo {
53
53
  */
54
54
  export function EntityDetailPanel({
55
55
  run,
56
+ hooks,
56
57
  onStreamClick,
57
58
  spanDetailData,
58
59
  spanDetailError,
@@ -64,6 +65,8 @@ export function EntityDetailPanel({
64
65
  selectedSpan,
65
66
  }: {
66
67
  run: WorkflowRun;
68
+ /** All hooks for the current run (used as fallback for token lookup). */
69
+ hooks?: Hook[];
67
70
  /** Callback when a stream reference is clicked */
68
71
  onStreamClick?: (streamId: string) => void;
69
72
  /** Pre-fetched span detail data for the selected span. */
@@ -96,6 +99,11 @@ export function EntityDetailPanel({
96
99
  const [stoppingSleep, setStoppingSleep] = useState(false);
97
100
  const [showResolveHookModal, setShowResolveHookModal] = useState(false);
98
101
  const [resolvingHook, setResolvingHook] = useState(false);
102
+ // Track hooks that were resolved in this session so the button hides
103
+ // immediately without waiting for the next event poll.
104
+ const [resolvedHookIds, setResolvedHookIds] = useState<Set<string>>(
105
+ new Set()
106
+ );
99
107
 
100
108
  const data = selectedSpan?.data;
101
109
  const rawEvents = selectedSpan?.rawEvents;
@@ -167,7 +175,11 @@ export function EntityDetailPanel({
167
175
  // Check if this hook can be resolved
168
176
  const canResolveHook = useMemo(() => {
169
177
  void rawEventsLength;
170
- if (resource !== 'hook' || !rawEvents) return false;
178
+ if (resource !== 'hook' || !rawEvents || !resourceId) return false;
179
+
180
+ // Check if we already resolved this hook in this session
181
+ if (resolvedHookIds.has(resourceId)) return false;
182
+
171
183
  const terminalStates = ['completed', 'failed', 'cancelled'];
172
184
  if (terminalStates.includes(run.status)) return false;
173
185
  const hasHookDisposed = rawEvents.some(
@@ -175,17 +187,36 @@ export function EntityDetailPanel({
175
187
  );
176
188
  if (hasHookDisposed) return false;
177
189
  return true;
178
- }, [resource, rawEvents, rawEventsLength, run.status]);
190
+ }, [
191
+ resource,
192
+ resourceId,
193
+ rawEvents,
194
+ rawEventsLength,
195
+ run.status,
196
+ resolvedHookIds,
197
+ ]);
179
198
 
180
199
  const error = spanDetailError ?? undefined;
181
200
  const loading = spanDetailLoading ?? false;
182
201
 
183
- // Get the hook token for resolving
202
+ // Get the hook token for resolving (prefer fetched data, then hooks array fallback)
184
203
  const hookToken = useMemo(() => {
185
- if (resource !== 'hook') return undefined;
186
- const candidate = spanDetailData ?? data;
187
- return isHook(candidate) ? candidate.token : undefined;
188
- }, [resource, spanDetailData, data]);
204
+ if (resource !== 'hook' || !resourceId) return undefined;
205
+ // 1. Try the externally-fetched detail data first
206
+ if (isHook(spanDetailData) && spanDetailData.token) {
207
+ return spanDetailData.token;
208
+ }
209
+ // 2. Try the hooks array (always has tokens)
210
+ const hookFromArray = hooks?.find((h) => h.hookId === resourceId);
211
+ if (hookFromArray?.token) {
212
+ return hookFromArray.token;
213
+ }
214
+ // 3. Try the span's inline data (partial hook from events - may lack token)
215
+ if (isHook(data) && (data as Hook).token) {
216
+ return (data as Hook).token;
217
+ }
218
+ return undefined;
219
+ }, [resource, resourceId, spanDetailData, data, hooks]);
189
220
 
190
221
  useEffect(() => {
191
222
  if (error && selectedSpan && resource) {
@@ -254,6 +285,10 @@ export function EntityDetailPanel({
254
285
  description: 'The payload has been sent and the hook resolved.',
255
286
  });
256
287
  setShowResolveHookModal(false);
288
+ // Mark this hook as resolved locally so the button hides immediately
289
+ if (resourceId) {
290
+ setResolvedHookIds((prev) => new Set(prev).add(resourceId));
291
+ }
257
292
  } catch (err) {
258
293
  console.error('Failed to resolve hook:', err);
259
294
  toast.error('Failed to resolve hook', {
@@ -271,34 +306,58 @@ export function EntityDetailPanel({
271
306
  return null;
272
307
  }
273
308
 
274
- const displayData = (spanDetailData ?? data) as
275
- | WorkflowRun
276
- | Step
277
- | Hook
278
- | Event;
309
+ // For sleep spans, spanDetailData from the host is typically an events array
310
+ // (not a single entity), so always prefer the inline wait entity from span
311
+ // attributes which contains waitId, runId, createdAt, resumeAt, completedAt.
312
+ const displayData = (
313
+ resource === 'sleep' ? data : (spanDetailData ?? data)
314
+ ) as WorkflowRun | Step | Hook | Event;
315
+ const moduleSpecifier = useMemo(() => {
316
+ const displayRecord = displayData as Record<string, unknown>;
317
+ const displayStepName = displayRecord.stepName;
318
+ const displayWorkflowName = displayRecord.workflowName;
319
+ if (typeof displayStepName === 'string') {
320
+ return displayStepName;
321
+ }
322
+ if (typeof displayWorkflowName === 'string') {
323
+ return displayWorkflowName;
324
+ }
325
+ if (typeof run.workflowName === 'string') {
326
+ return run.workflowName;
327
+ }
328
+ return undefined;
329
+ }, [displayData, run.workflowName]);
279
330
 
280
331
  return (
281
- <div className={clsx('flex flex-col px-2')}>
332
+ <div
333
+ className={clsx('flex flex-col px-3')}
334
+ style={{ paddingTop: 12, gap: 16 }}
335
+ >
282
336
  {/* Wake up button for pending sleep calls */}
283
337
  {resource === 'sleep' && canWakeUp && (
284
- <div className="mb-3 pb-3 border-b border-gray-200 dark:border-gray-700">
338
+ <div
339
+ className="mb-3 pb-3"
340
+ style={{ borderBottom: '1px solid var(--ds-gray-alpha-400)' }}
341
+ >
285
342
  <button
286
343
  type="button"
287
344
  onClick={handleWakeUp}
288
345
  disabled={stoppingSleep}
289
346
  className={clsx(
290
347
  'flex items-center gap-2 px-3 py-2 text-sm font-medium rounded-md w-full',
291
- 'bg-amber-100 dark:bg-amber-900/30 text-amber-800 dark:text-amber-200',
292
- 'hover:bg-amber-200 dark:hover:bg-amber-900/50',
293
348
  'disabled:opacity-50 disabled:cursor-not-allowed',
294
349
  'transition-colors',
295
350
  stoppingSleep ? 'opacity-50 cursor-not-allowed' : 'cursor-pointer'
296
351
  )}
352
+ style={{
353
+ background: 'var(--ds-amber-200)',
354
+ color: 'var(--ds-amber-900)',
355
+ }}
297
356
  >
298
357
  <Zap className="h-4 w-4" />
299
358
  {stoppingSleep ? 'Waking up...' : 'Wake up'}
300
359
  </button>
301
- <p className="mt-1.5 text-xs text-gray-500 dark:text-gray-400">
360
+ <p className="mt-1.5 text-xs" style={{ color: 'var(--ds-gray-900)' }}>
302
361
  Interrupt this sleep call and wake up the run.
303
362
  </p>
304
363
  </div>
@@ -306,23 +365,29 @@ export function EntityDetailPanel({
306
365
 
307
366
  {/* Resolve hook button for pending hooks */}
308
367
  {resource === 'hook' && canResolveHook && (
309
- <div className="mb-3 pb-3 border-b border-gray-200 dark:border-gray-700">
368
+ <div
369
+ className="mb-3 pb-3"
370
+ style={{ borderBottom: '1px solid var(--ds-gray-alpha-400)' }}
371
+ >
310
372
  <button
311
373
  type="button"
312
374
  onClick={() => setShowResolveHookModal(true)}
313
375
  disabled={resolvingHook}
314
376
  className={clsx(
315
377
  'flex items-center gap-2 px-3 py-2 text-sm font-medium rounded-md w-full',
316
- 'bg-primary text-primary-foreground hover:bg-primary/90',
317
378
  'disabled:opacity-50 disabled:cursor-not-allowed',
318
379
  'transition-colors',
319
380
  resolvingHook ? 'opacity-50 cursor-not-allowed' : 'cursor-pointer'
320
381
  )}
382
+ style={{
383
+ background: 'var(--ds-gray-1000)',
384
+ color: 'var(--ds-background-100)',
385
+ }}
321
386
  >
322
387
  <Send className="h-4 w-4" />
323
388
  Resolve Hook
324
389
  </button>
325
- <p className="mt-1.5 text-xs text-gray-500 dark:text-gray-400">
390
+ <p className="mt-1.5 text-xs" style={{ color: 'var(--ds-gray-900)' }}>
326
391
  Send a JSON payload to resolve this hook.
327
392
  </p>
328
393
  </div>
@@ -339,6 +404,7 @@ export function EntityDetailPanel({
339
404
  {/* Content display */}
340
405
  <AttributePanel
341
406
  data={displayData}
407
+ moduleSpecifier={moduleSpecifier}
342
408
  expiredAt={run.expiredAt}
343
409
  isLoading={loading}
344
410
  error={error ?? undefined}
@@ -2,9 +2,7 @@
2
2
 
3
3
  import type { Event } from '@workflow/world';
4
4
  import { useCallback, useMemo, useState } from 'react';
5
- import { ObjectInspector } from 'react-inspector';
6
- import { useDarkMode } from '../../hooks/use-dark-mode';
7
- import { inspectorThemeDark, inspectorThemeLight } from '../ui/inspector-theme';
5
+ import { DataInspector } from '../ui/data-inspector';
8
6
  import { localMillisecondTime } from './attribute-panel';
9
7
  import { DetailCard } from './detail-card';
10
8
 
@@ -15,6 +13,8 @@ const DATA_EVENT_TYPES = new Set([
15
13
  'step_created',
16
14
  'step_completed',
17
15
  'step_failed',
16
+ 'hook_created',
17
+ 'hook_received',
18
18
  'run_created',
19
19
  'run_completed',
20
20
  ]);
@@ -32,7 +32,6 @@ function EventItem({
32
32
  eventId: string
33
33
  ) => Promise<unknown | null>;
34
34
  }) {
35
- const isDark = useDarkMode();
36
35
  const [loadedData, setLoadedData] = useState<unknown | null>(null);
37
36
  const [isLoading, setIsLoading] = useState(false);
38
37
  const [loadError, setLoadError] = useState<string | null>(null);
@@ -170,13 +169,7 @@ function EventItem({
170
169
  className="mt-2 overflow-x-auto rounded-md border p-3"
171
170
  style={{ borderColor: 'var(--ds-gray-300)' }}
172
171
  >
173
- <ObjectInspector
174
- data={displayData}
175
- // @ts-expect-error react-inspector accepts theme objects at runtime
176
- // see https://github.com/storybookjs/react-inspector/blob/main/README.md#theme
177
- theme={isDark ? inspectorThemeDark : inspectorThemeLight}
178
- expandLevel={2}
179
- />
172
+ <DataInspector data={displayData} />
180
173
  </div>
181
174
  )}
182
175
  </DetailCard>
@@ -1,6 +1,5 @@
1
1
  'use client';
2
2
 
3
- import clsx from 'clsx';
4
3
  import { Send, X } from 'lucide-react';
5
4
  import { useCallback, useEffect, useRef, useState } from 'react';
6
5
 
@@ -17,6 +16,9 @@ interface ResolveHookModalProps {
17
16
 
18
17
  /**
19
18
  * Modal component for resolving a hook by entering a JSON payload.
19
+ *
20
+ * Styled to match the Geist design-system dialog component used in the
21
+ * Vercel dashboard so it looks native when rendered inside `front`.
20
22
  */
21
23
  export function ResolveHookModal({
22
24
  isOpen,
@@ -82,6 +84,15 @@ export function ResolveHookModal({
82
84
  [submitPayload]
83
85
  );
84
86
 
87
+ const isMacPlatform =
88
+ typeof navigator !== 'undefined' &&
89
+ (
90
+ (navigator as Navigator & { userAgentData?: { platform?: string } })
91
+ .userAgentData?.platform ?? navigator.userAgent
92
+ )
93
+ .toLowerCase()
94
+ .includes('mac');
95
+
85
96
  // Handle Cmd/Ctrl + Enter to submit
86
97
  const handleKeyDown = useCallback(
87
98
  (e: React.KeyboardEvent) => {
@@ -99,30 +110,60 @@ export function ResolveHookModal({
99
110
 
100
111
  return (
101
112
  <div
102
- className="fixed inset-0 z-50 flex items-center justify-center"
113
+ style={{
114
+ position: 'fixed',
115
+ inset: 0,
116
+ zIndex: 50,
117
+ display: 'flex',
118
+ alignItems: 'center',
119
+ justifyContent: 'center',
120
+ }}
103
121
  role="dialog"
104
122
  aria-modal="true"
105
123
  aria-labelledby="resolve-hook-modal-title"
106
124
  >
107
- {/* Backdrop */}
125
+ {/* Backdrop — matches Geist dialog ::backdrop */}
108
126
  <div
109
- className="absolute inset-0 bg-black/50 backdrop-blur-sm"
127
+ style={{
128
+ position: 'absolute',
129
+ inset: 0,
130
+ background: 'rgba(0, 0, 0, 0.7)',
131
+ }}
110
132
  onClick={isSubmitting ? undefined : onClose}
111
133
  />
112
134
 
113
- {/* Modal content */}
135
+ {/* Modal card — matches Geist dialog.geist-dialog */}
114
136
  <div
115
- className={clsx(
116
- 'relative z-10 w-full max-w-lg mx-4',
117
- 'bg-background text-foreground rounded-lg shadow-xl',
118
- 'border border-border'
119
- )}
137
+ style={{
138
+ position: 'relative',
139
+ zIndex: 10,
140
+ width: 480,
141
+ maxWidth: 'calc(100% - 32px)',
142
+ borderRadius: 12,
143
+ border: 'none',
144
+ boxShadow: 'var(--ds-shadow-menu)',
145
+ background: 'var(--ds-background-100)',
146
+ color: 'var(--ds-gray-1000)',
147
+ overflow: 'hidden',
148
+ }}
120
149
  >
121
150
  {/* Header */}
122
- <div className="flex items-center justify-between px-4 py-3 border-b border-border">
151
+ <div
152
+ style={{
153
+ display: 'flex',
154
+ alignItems: 'center',
155
+ justifyContent: 'space-between',
156
+ padding: '16px 24px',
157
+ }}
158
+ >
123
159
  <h2
124
160
  id="resolve-hook-modal-title"
125
- className="text-lg font-semibold text-foreground"
161
+ style={{
162
+ margin: 0,
163
+ fontSize: 16,
164
+ fontWeight: 600,
165
+ color: 'var(--ds-gray-1000)',
166
+ }}
126
167
  >
127
168
  Resolve Hook
128
169
  </h2>
@@ -130,30 +171,69 @@ export function ResolveHookModal({
130
171
  type="button"
131
172
  onClick={onClose}
132
173
  disabled={isSubmitting}
133
- className={clsx(
134
- 'p-1 rounded-md transition-colors',
135
- 'text-muted-foreground hover:text-foreground',
136
- 'hover:bg-muted',
137
- 'disabled:opacity-50 disabled:cursor-not-allowed'
138
- )}
139
174
  aria-label="Close modal"
175
+ style={{
176
+ display: 'flex',
177
+ alignItems: 'center',
178
+ justifyContent: 'center',
179
+ padding: 4,
180
+ borderRadius: 6,
181
+ border: 'none',
182
+ background: 'transparent',
183
+ color: 'var(--ds-gray-900)',
184
+ cursor: isSubmitting ? 'not-allowed' : 'pointer',
185
+ opacity: isSubmitting ? 0.5 : 1,
186
+ transition: 'background 0.15s',
187
+ }}
188
+ onMouseEnter={(e) => {
189
+ e.currentTarget.style.background = 'var(--ds-gray-alpha-200)';
190
+ }}
191
+ onMouseLeave={(e) => {
192
+ e.currentTarget.style.background = 'transparent';
193
+ }}
140
194
  >
141
- <X className="h-5 w-5" />
195
+ <X style={{ width: 16, height: 16 }} />
142
196
  </button>
143
197
  </div>
144
198
 
145
199
  {/* Body */}
146
200
  <form onSubmit={handleSubmit}>
147
- <div className="px-4 py-4">
201
+ <div style={{ padding: '0 24px 16px' }}>
148
202
  <label
149
203
  htmlFor="json-payload"
150
- className="block text-sm font-medium text-foreground mb-2"
204
+ style={{
205
+ display: 'block',
206
+ fontSize: 14,
207
+ fontWeight: 500,
208
+ marginBottom: 6,
209
+ color: 'var(--ds-gray-1000)',
210
+ }}
151
211
  >
152
212
  JSON Payload
153
213
  </label>
154
- <p className="text-xs text-muted-foreground mb-3">
214
+ <p
215
+ style={{
216
+ fontSize: 13,
217
+ marginBottom: 12,
218
+ marginTop: 0,
219
+ color: 'var(--ds-gray-900)',
220
+ lineHeight: 1.5,
221
+ }}
222
+ >
155
223
  Enter a JSON value to send to the hook. Leave empty to send{' '}
156
- <code className="px-1 py-0.5 bg-muted rounded text-xs">null</code>
224
+ <code
225
+ style={{
226
+ padding: '2px 6px',
227
+ borderRadius: 4,
228
+ fontSize: 12,
229
+ fontFamily:
230
+ 'var(--font-mono, ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace)',
231
+ background: 'var(--ds-gray-alpha-200)',
232
+ color: 'var(--ds-gray-1000)',
233
+ }}
234
+ >
235
+ null
236
+ </code>
157
237
  .
158
238
  </p>
159
239
  <textarea
@@ -167,34 +247,97 @@ export function ResolveHookModal({
167
247
  onKeyDown={handleKeyDown}
168
248
  disabled={isSubmitting}
169
249
  placeholder='{"key": "value"}'
170
- className={clsx(
171
- 'w-full h-40 px-3 py-2 font-mono text-sm',
172
- 'text-foreground',
173
- 'bg-background',
174
- 'border rounded-md',
175
- 'focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 focus:ring-offset-background',
176
- 'disabled:opacity-50 disabled:cursor-not-allowed',
177
- 'placeholder:text-muted-foreground',
178
- parseError ? 'border-destructive' : 'border-input'
179
- )}
250
+ style={{
251
+ width: '100%',
252
+ height: 160,
253
+ padding: '8px 12px',
254
+ fontFamily:
255
+ 'var(--font-mono, ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace)',
256
+ fontSize: 13,
257
+ lineHeight: 1.5,
258
+ borderRadius: 8,
259
+ border: `1px solid ${parseError ? 'var(--ds-red-700)' : 'var(--ds-gray-alpha-400)'}`,
260
+ color: 'var(--ds-gray-1000)',
261
+ background: 'var(--ds-background-100)',
262
+ outline: 'none',
263
+ resize: 'none',
264
+ opacity: isSubmitting ? 0.5 : 1,
265
+ cursor: isSubmitting ? 'not-allowed' : 'text',
266
+ boxSizing: 'border-box',
267
+ }}
180
268
  />
181
269
  {parseError && (
182
- <p className="mt-2 text-sm text-destructive">{parseError}</p>
270
+ <p
271
+ style={{
272
+ marginTop: 8,
273
+ fontSize: 13,
274
+ color: 'var(--ds-red-900)',
275
+ margin: '8px 0 0',
276
+ }}
277
+ >
278
+ {parseError}
279
+ </p>
183
280
  )}
281
+ <p
282
+ style={{
283
+ marginTop: 8,
284
+ fontSize: 12,
285
+ color: 'var(--ds-gray-800)',
286
+ margin: '8px 0 0',
287
+ }}
288
+ >
289
+ Press{' '}
290
+ <kbd
291
+ style={{
292
+ padding: '2px 5px',
293
+ borderRadius: 4,
294
+ fontSize: 11,
295
+ fontFamily:
296
+ 'var(--font-mono, ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace)',
297
+ background: 'var(--ds-gray-alpha-200)',
298
+ border: '1px solid var(--ds-gray-alpha-400)',
299
+ }}
300
+ >
301
+ {isMacPlatform ? '⌘' : 'Ctrl'}
302
+ +Enter
303
+ </kbd>{' '}
304
+ to submit
305
+ </p>
184
306
  </div>
185
307
 
186
308
  {/* Footer */}
187
- <div className="flex items-center justify-end gap-2 px-4 py-3 border-t border-border">
309
+ <div
310
+ style={{
311
+ display: 'flex',
312
+ alignItems: 'center',
313
+ justifyContent: 'flex-end',
314
+ gap: 8,
315
+ padding: '12px 24px',
316
+ borderTop: '1px solid var(--ds-gray-alpha-400)',
317
+ }}
318
+ >
188
319
  <button
189
320
  type="button"
190
321
  onClick={onClose}
191
322
  disabled={isSubmitting}
192
- className={clsx(
193
- 'px-4 py-2 text-sm font-medium rounded-md transition-colors',
194
- 'bg-secondary text-secondary-foreground',
195
- 'hover:bg-secondary/80',
196
- 'disabled:opacity-50 disabled:cursor-not-allowed'
197
- )}
323
+ style={{
324
+ padding: '8px 16px',
325
+ fontSize: 14,
326
+ fontWeight: 500,
327
+ borderRadius: 8,
328
+ border: '1px solid var(--ds-gray-alpha-400)',
329
+ background: 'var(--ds-background-100)',
330
+ color: 'var(--ds-gray-1000)',
331
+ cursor: isSubmitting ? 'not-allowed' : 'pointer',
332
+ opacity: isSubmitting ? 0.5 : 1,
333
+ transition: 'background 0.15s',
334
+ }}
335
+ onMouseEnter={(e) => {
336
+ e.currentTarget.style.background = 'var(--ds-gray-alpha-100)';
337
+ }}
338
+ onMouseLeave={(e) => {
339
+ e.currentTarget.style.background = 'var(--ds-background-100)';
340
+ }}
198
341
  >
199
342
  Cancel
200
343
  </button>
@@ -202,13 +345,29 @@ export function ResolveHookModal({
202
345
  type="button"
203
346
  onClick={() => void submitPayload()}
204
347
  disabled={isSubmitting}
205
- className={clsx(
206
- 'flex items-center gap-2 px-3 py-2 text-sm font-medium rounded-md transition-colors',
207
- 'bg-primary text-primary-foreground hover:bg-primary/90',
208
- 'disabled:opacity-50 disabled:cursor-not-allowed'
209
- )}
348
+ style={{
349
+ display: 'flex',
350
+ alignItems: 'center',
351
+ gap: 6,
352
+ padding: '8px 16px',
353
+ fontSize: 14,
354
+ fontWeight: 500,
355
+ borderRadius: 8,
356
+ border: 'none',
357
+ background: 'var(--ds-gray-1000)',
358
+ color: 'var(--ds-background-100)',
359
+ cursor: isSubmitting ? 'not-allowed' : 'pointer',
360
+ opacity: isSubmitting ? 0.5 : 1,
361
+ transition: 'opacity 0.15s',
362
+ }}
363
+ onMouseEnter={(e) => {
364
+ if (!isSubmitting) e.currentTarget.style.opacity = '0.9';
365
+ }}
366
+ onMouseLeave={(e) => {
367
+ if (!isSubmitting) e.currentTarget.style.opacity = '1';
368
+ }}
210
369
  >
211
- <Send className="h-4 w-4" />
370
+ <Send style={{ width: 14, height: 14 }} />
212
371
  {isSubmitting ? 'Sending...' : 'Send Payload'}
213
372
  </button>
214
373
  </div>