@workflow/web-shared 5.0.0-beta.6 → 5.0.0-beta.8

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 (200) hide show
  1. package/dist/components/error-boundary.js +1 -1
  2. package/dist/components/event-list-view.d.ts +12 -0
  3. package/dist/components/event-list-view.d.ts.map +1 -1
  4. package/dist/components/event-list-view.js +26 -13
  5. package/dist/components/hook-actions.js +1 -1
  6. package/dist/components/index.js +1 -1
  7. package/dist/components/new-trace-viewer/components/copy-button.js +2 -2
  8. package/dist/components/new-trace-viewer/components/event-list.js +3 -3
  9. package/dist/components/new-trace-viewer/components/middle-truncate/copy-selection.js +1 -1
  10. package/dist/components/new-trace-viewer/components/middle-truncate/middle-truncate.js +1 -1
  11. package/dist/components/new-trace-viewer/components/middle-truncate/truncate.js +1 -1
  12. package/dist/components/new-trace-viewer/components/middle-truncate/use-middle-truncate.js +1 -1
  13. package/dist/components/new-trace-viewer/components/split-pane.js +1 -1
  14. package/dist/components/new-trace-viewer/components/timeline.d.ts +1 -1
  15. package/dist/components/new-trace-viewer/components/timeline.d.ts.map +1 -1
  16. package/dist/components/new-trace-viewer/components/timeline.js +187 -103
  17. package/dist/components/new-trace-viewer/context.js +1 -1
  18. package/dist/components/new-trace-viewer/detail-panel.js +2 -2
  19. package/dist/components/new-trace-viewer/icons.js +1 -1
  20. package/dist/components/new-trace-viewer/trace-viewer.d.ts.map +1 -1
  21. package/dist/components/new-trace-viewer/trace-viewer.js +53 -26
  22. package/dist/components/new-trace-viewer/types.js +1 -1
  23. package/dist/components/new-trace-viewer/util/constants.js +1 -1
  24. package/dist/components/new-trace-viewer/utils.d.ts +8 -4
  25. package/dist/components/new-trace-viewer/utils.d.ts.map +1 -1
  26. package/dist/components/new-trace-viewer/utils.js +31 -6
  27. package/dist/components/run-trace-view.js +1 -1
  28. package/dist/components/sidebar/attribute-panel.d.ts.map +1 -1
  29. package/dist/components/sidebar/attribute-panel.js +99 -73
  30. package/dist/components/sidebar/conversation-view.js +1 -1
  31. package/dist/components/sidebar/copyable-data-block.d.ts +1 -0
  32. package/dist/components/sidebar/copyable-data-block.d.ts.map +1 -1
  33. package/dist/components/sidebar/copyable-data-block.js +16 -15
  34. package/dist/components/sidebar/detail-card.d.ts +5 -6
  35. package/dist/components/sidebar/detail-card.d.ts.map +1 -1
  36. package/dist/components/sidebar/detail-card.js +32 -16
  37. package/dist/components/sidebar/entity-detail-panel.d.ts.map +1 -1
  38. package/dist/components/sidebar/entity-detail-panel.js +6 -3
  39. package/dist/components/sidebar/events-list.d.ts.map +1 -1
  40. package/dist/components/sidebar/events-list.js +21 -15
  41. package/dist/components/sidebar/resolve-hook-modal.js +1 -1
  42. package/dist/components/sidebar/sidebar-data-context.js +1 -1
  43. package/dist/components/stream-viewer.js +1 -1
  44. package/dist/components/trace-viewer/components/map.js +1 -1
  45. package/dist/components/trace-viewer/components/markers.js +1 -1
  46. package/dist/components/trace-viewer/components/node.js +1 -1
  47. package/dist/components/trace-viewer/components/search-input.js +1 -1
  48. package/dist/components/trace-viewer/components/search.js +1 -1
  49. package/dist/components/trace-viewer/components/span-content.js +1 -1
  50. package/dist/components/trace-viewer/components/span-detail-panel.js +1 -1
  51. package/dist/components/trace-viewer/components/span-segments.js +1 -1
  52. package/dist/components/trace-viewer/components/span-strategies.js +1 -1
  53. package/dist/components/trace-viewer/components/ui.js +1 -1
  54. package/dist/components/trace-viewer/components/zoom-button.js +1 -1
  55. package/dist/components/trace-viewer/components/zoom-icons.js +1 -1
  56. package/dist/components/trace-viewer/context.js +1 -1
  57. package/dist/components/trace-viewer/index.js +1 -1
  58. package/dist/components/trace-viewer/trace-viewer.js +1 -1
  59. package/dist/components/trace-viewer/types.js +1 -1
  60. package/dist/components/trace-viewer/util/constants.js +1 -1
  61. package/dist/components/trace-viewer/util/scrollbar-width.js +1 -1
  62. package/dist/components/trace-viewer/util/timing.js +1 -1
  63. package/dist/components/trace-viewer/util/tree.js +1 -1
  64. package/dist/components/trace-viewer/util/use-immediate-style.js +1 -1
  65. package/dist/components/trace-viewer/util/use-streaming-spans.js +1 -1
  66. package/dist/components/trace-viewer/util/use-trackpad-zoom.js +1 -1
  67. package/dist/components/trace-viewer/worker.js +1 -1
  68. package/dist/components/trace-viewer-new.js +1 -1
  69. package/dist/components/ui/alert.js +1 -1
  70. package/dist/components/ui/button.d.ts +10 -0
  71. package/dist/components/ui/button.d.ts.map +1 -0
  72. package/dist/components/ui/button.js +39 -0
  73. package/dist/components/ui/card.js +1 -1
  74. package/dist/components/ui/data-inspector.d.ts +1 -0
  75. package/dist/components/ui/data-inspector.d.ts.map +1 -1
  76. package/dist/components/ui/data-inspector.js +4 -9
  77. package/dist/components/ui/decrypt-button.js +1 -1
  78. package/dist/components/ui/error-card.js +1 -1
  79. package/dist/components/ui/error-stack-block.d.ts.map +1 -1
  80. package/dist/components/ui/error-stack-block.js +4 -18
  81. package/dist/components/ui/inspector-theme.js +1 -1
  82. package/dist/components/ui/load-more-button.js +1 -1
  83. package/dist/components/ui/menu-dropdown.js +1 -1
  84. package/dist/components/ui/skeleton.js +1 -1
  85. package/dist/components/ui/spinner.js +1 -1
  86. package/dist/components/ui/timestamp-tooltip.js +1 -1
  87. package/dist/components/workflow-trace-view.js +1 -1
  88. package/dist/components/workflow-traces/event-colors.js +1 -1
  89. package/dist/components/workflow-traces/trace-colors.js +1 -1
  90. package/dist/components/workflow-traces/trace-span-construction.js +1 -1
  91. package/dist/components/workflow-traces/trace-time-utils.js +1 -1
  92. package/dist/hooks/use-dark-mode.js +1 -1
  93. package/dist/hooks/use-reduced-motion.d.ts +10 -0
  94. package/dist/hooks/use-reduced-motion.d.ts.map +1 -0
  95. package/dist/hooks/use-reduced-motion.js +29 -0
  96. package/dist/index.js +1 -1
  97. package/dist/lib/event-analysis.js +1 -1
  98. package/dist/lib/event-materialization.js +1 -1
  99. package/dist/lib/hydration.js +1 -1
  100. package/dist/lib/stream-display.js +1 -1
  101. package/dist/lib/toast.js +1 -1
  102. package/dist/lib/trace-builder.js +1 -1
  103. package/dist/lib/utils.js +1 -1
  104. package/package.json +5 -5
  105. package/src/components/event-list-view.tsx +29 -13
  106. package/src/components/new-trace-viewer/components/copy-button.tsx +1 -1
  107. package/src/components/new-trace-viewer/components/event-list.tsx +2 -2
  108. package/src/components/new-trace-viewer/components/timeline.tsx +353 -192
  109. package/src/components/new-trace-viewer/detail-panel.tsx +1 -1
  110. package/src/components/new-trace-viewer/trace-viewer.tsx +93 -59
  111. package/src/components/new-trace-viewer/utils.ts +40 -8
  112. package/src/components/sidebar/attribute-panel.tsx +212 -235
  113. package/src/components/sidebar/copyable-data-block.tsx +49 -27
  114. package/src/components/sidebar/detail-card.tsx +119 -41
  115. package/src/components/sidebar/entity-detail-panel.tsx +28 -28
  116. package/src/components/sidebar/events-list.tsx +51 -79
  117. package/src/components/ui/button.tsx +61 -0
  118. package/src/components/ui/data-inspector.tsx +9 -18
  119. package/src/components/ui/error-stack-block.tsx +7 -26
  120. package/src/hooks/use-reduced-motion.ts +34 -0
  121. package/dist/components/error-boundary.js.map +0 -1
  122. package/dist/components/event-list-view.js.map +0 -1
  123. package/dist/components/hook-actions.js.map +0 -1
  124. package/dist/components/index.js.map +0 -1
  125. package/dist/components/new-trace-viewer/components/copy-button.js.map +0 -1
  126. package/dist/components/new-trace-viewer/components/event-list.js.map +0 -1
  127. package/dist/components/new-trace-viewer/components/middle-truncate/copy-selection.js.map +0 -1
  128. package/dist/components/new-trace-viewer/components/middle-truncate/middle-truncate.js.map +0 -1
  129. package/dist/components/new-trace-viewer/components/middle-truncate/truncate.js.map +0 -1
  130. package/dist/components/new-trace-viewer/components/middle-truncate/use-middle-truncate.js.map +0 -1
  131. package/dist/components/new-trace-viewer/components/split-pane.js.map +0 -1
  132. package/dist/components/new-trace-viewer/components/timeline.js.map +0 -1
  133. package/dist/components/new-trace-viewer/context.js.map +0 -1
  134. package/dist/components/new-trace-viewer/detail-panel.js.map +0 -1
  135. package/dist/components/new-trace-viewer/icons.js.map +0 -1
  136. package/dist/components/new-trace-viewer/trace-viewer.js.map +0 -1
  137. package/dist/components/new-trace-viewer/types.js.map +0 -1
  138. package/dist/components/new-trace-viewer/util/constants.js.map +0 -1
  139. package/dist/components/new-trace-viewer/utils.js.map +0 -1
  140. package/dist/components/run-trace-view.js.map +0 -1
  141. package/dist/components/sidebar/attribute-panel.js.map +0 -1
  142. package/dist/components/sidebar/conversation-view.js.map +0 -1
  143. package/dist/components/sidebar/copyable-data-block.js.map +0 -1
  144. package/dist/components/sidebar/detail-card.js.map +0 -1
  145. package/dist/components/sidebar/entity-detail-panel.js.map +0 -1
  146. package/dist/components/sidebar/events-list.js.map +0 -1
  147. package/dist/components/sidebar/resolve-hook-modal.js.map +0 -1
  148. package/dist/components/sidebar/sidebar-data-context.js.map +0 -1
  149. package/dist/components/stream-viewer.js.map +0 -1
  150. package/dist/components/trace-viewer/components/map.js.map +0 -1
  151. package/dist/components/trace-viewer/components/markers.js.map +0 -1
  152. package/dist/components/trace-viewer/components/node.js.map +0 -1
  153. package/dist/components/trace-viewer/components/search-input.js.map +0 -1
  154. package/dist/components/trace-viewer/components/search.js.map +0 -1
  155. package/dist/components/trace-viewer/components/span-content.js.map +0 -1
  156. package/dist/components/trace-viewer/components/span-detail-panel.js.map +0 -1
  157. package/dist/components/trace-viewer/components/span-segments.js.map +0 -1
  158. package/dist/components/trace-viewer/components/span-strategies.js.map +0 -1
  159. package/dist/components/trace-viewer/components/ui.js.map +0 -1
  160. package/dist/components/trace-viewer/components/zoom-button.js.map +0 -1
  161. package/dist/components/trace-viewer/components/zoom-icons.js.map +0 -1
  162. package/dist/components/trace-viewer/context.js.map +0 -1
  163. package/dist/components/trace-viewer/index.js.map +0 -1
  164. package/dist/components/trace-viewer/trace-viewer.js.map +0 -1
  165. package/dist/components/trace-viewer/types.js.map +0 -1
  166. package/dist/components/trace-viewer/util/constants.js.map +0 -1
  167. package/dist/components/trace-viewer/util/scrollbar-width.js.map +0 -1
  168. package/dist/components/trace-viewer/util/timing.js.map +0 -1
  169. package/dist/components/trace-viewer/util/tree.js.map +0 -1
  170. package/dist/components/trace-viewer/util/use-immediate-style.js.map +0 -1
  171. package/dist/components/trace-viewer/util/use-streaming-spans.js.map +0 -1
  172. package/dist/components/trace-viewer/util/use-trackpad-zoom.js.map +0 -1
  173. package/dist/components/trace-viewer/worker.js.map +0 -1
  174. package/dist/components/trace-viewer-new.js.map +0 -1
  175. package/dist/components/ui/alert.js.map +0 -1
  176. package/dist/components/ui/card.js.map +0 -1
  177. package/dist/components/ui/data-inspector.js.map +0 -1
  178. package/dist/components/ui/decrypt-button.js.map +0 -1
  179. package/dist/components/ui/error-card.js.map +0 -1
  180. package/dist/components/ui/error-stack-block.js.map +0 -1
  181. package/dist/components/ui/inspector-theme.js.map +0 -1
  182. package/dist/components/ui/load-more-button.js.map +0 -1
  183. package/dist/components/ui/menu-dropdown.js.map +0 -1
  184. package/dist/components/ui/skeleton.js.map +0 -1
  185. package/dist/components/ui/spinner.js.map +0 -1
  186. package/dist/components/ui/timestamp-tooltip.js.map +0 -1
  187. package/dist/components/workflow-trace-view.js.map +0 -1
  188. package/dist/components/workflow-traces/event-colors.js.map +0 -1
  189. package/dist/components/workflow-traces/trace-colors.js.map +0 -1
  190. package/dist/components/workflow-traces/trace-span-construction.js.map +0 -1
  191. package/dist/components/workflow-traces/trace-time-utils.js.map +0 -1
  192. package/dist/hooks/use-dark-mode.js.map +0 -1
  193. package/dist/index.js.map +0 -1
  194. package/dist/lib/event-analysis.js.map +0 -1
  195. package/dist/lib/event-materialization.js.map +0 -1
  196. package/dist/lib/hydration.js.map +0 -1
  197. package/dist/lib/stream-display.js.map +0 -1
  198. package/dist/lib/toast.js.map +0 -1
  199. package/dist/lib/trace-builder.js.map +0 -1
  200. package/dist/lib/utils.js.map +0 -1
@@ -3,7 +3,6 @@
3
3
  import { parseStepName, parseWorkflowName } from '@workflow/utils/parse-name';
4
4
  import type { Event, Hook, Step, WorkflowRun } from '@workflow/world';
5
5
  import type { ModelMessage } from 'ai';
6
- import { Lock } from 'lucide-react';
7
6
  import type { KeyboardEvent, ReactNode } from 'react';
8
7
  import { useCallback, useContext, useMemo, useState } from 'react';
9
8
  import { isEncryptedMarker, isExpiredMarker } from '../../lib/hydration';
@@ -20,10 +19,11 @@ import {
20
19
  isStructuredErrorWithStack,
21
20
  } from '../ui/error-stack-block';
22
21
  import { Skeleton } from '../ui/skeleton';
23
- import { Spinner } from '../ui/spinner';
24
22
  import { TimestampTooltip } from '../ui/timestamp-tooltip';
23
+ import { CopyButton } from '../new-trace-viewer/components/copy-button';
24
+ import { MiddleTruncate } from '../new-trace-viewer/components/middle-truncate/middle-truncate';
25
25
  import { ConversationView } from './conversation-view';
26
- import { CopyableDataBlock } from './copyable-data-block';
26
+ import { CopyableDataBlock, EncryptedDataBlock } from './copyable-data-block';
27
27
  import { DetailCard } from './detail-card';
28
28
 
29
29
  /**
@@ -151,7 +151,7 @@ function ConversationWithTabs({
151
151
  );
152
152
 
153
153
  return (
154
- <DetailCard summary={`Input (${conversation.length} messages)`}>
154
+ <DetailCard summary="Input">
155
155
  <TabbedContainer
156
156
  tabs={conversationTabs}
157
157
  activeTab={activeTab}
@@ -180,51 +180,8 @@ function ConversationWithTabs({
180
180
  * Render a value with the shared DataInspector (ObjectInspector with
181
181
  * custom theming, nodeRenderer for StreamRef/ClassInstanceRef, etc.)
182
182
  */
183
- /**
184
- * Inline display for an encrypted field — no expand, just a flat label
185
- * with the lucide Lock icon matching the title bar Decrypt button.
186
- */
187
183
  function EncryptedFieldBlock() {
188
- const ctx = useContext(DecryptClickContext);
189
- if (ctx) {
190
- return (
191
- <button
192
- type="button"
193
- onClick={ctx.onDecrypt}
194
- disabled={ctx.isDecrypting}
195
- className="flex w-full items-center justify-center gap-1.5 rounded-md border px-3 py-2 text-xs cursor-pointer transition-colors"
196
- style={{
197
- borderColor: 'var(--ds-gray-400)',
198
- backgroundColor: 'var(--ds-gray-100)',
199
- color: 'var(--ds-gray-700)',
200
- opacity: ctx.isDecrypting ? 0.6 : 1,
201
- }}
202
- title="Click to decrypt"
203
- >
204
- {ctx.isDecrypting ? (
205
- <Spinner size={12} />
206
- ) : (
207
- <Lock className="h-3 w-3" />
208
- )}
209
- <span className="font-medium">
210
- {ctx.isDecrypting ? 'Decrypting…' : 'Decrypt'}
211
- </span>
212
- </button>
213
- );
214
- }
215
- return (
216
- <div
217
- className="flex w-full items-center justify-center gap-1.5 rounded-md border px-3 py-2 text-xs"
218
- style={{
219
- borderColor: 'var(--ds-gray-300)',
220
- backgroundColor: 'var(--ds-gray-100)',
221
- color: 'var(--ds-gray-700)',
222
- }}
223
- >
224
- <Lock className="h-3 w-3" />
225
- <span className="font-medium">Encrypted</span>
226
- </div>
227
- );
184
+ return <EncryptedDataBlock />;
228
185
  }
229
186
 
230
187
  /**
@@ -269,7 +226,9 @@ type AttributeKey =
269
226
  | 'workflowCoreVersion'
270
227
  | 'receivedCount'
271
228
  | 'lastReceivedAt'
272
- | 'disposedAt';
229
+ | 'disposedAt'
230
+ | 'isSystem'
231
+ | 'errorCode';
273
232
 
274
233
  const attributeOrder: AttributeKey[] = [
275
234
  'workflowName',
@@ -326,6 +285,7 @@ const attributeDisplayNames: Partial<Record<AttributeKey, string>> = {
326
285
  attempt: 'Attempts',
327
286
  eventId: 'Event ID',
328
287
  runId: 'Run ID',
288
+ token: 'Token',
329
289
  eventType: 'Event Type',
330
290
  correlationId: 'Correlation ID',
331
291
  deploymentId: 'Deployment ID',
@@ -432,7 +392,7 @@ const attributeToDisplayFn: Record<
432
392
  stepName: (_value: unknown) => null,
433
393
  // IDs
434
394
  runId: (_value: unknown) => null,
435
- stepId: (_value: unknown) => null,
395
+ stepId: (value: unknown) => String(value),
436
396
  hookId: (value: unknown) => String(value),
437
397
  eventId: (value: unknown) => String(value),
438
398
  // Run/step details
@@ -457,6 +417,13 @@ const attributeToDisplayFn: Record<
457
417
  projectId: (_value: unknown) => null,
458
418
  environment: (_value: unknown) => null,
459
419
  executionContext: (_value: unknown) => null,
420
+ // Attributes MVP — string-string metadata attached to the run.
421
+ // Rendered as a JSON block; if empty/missing, hidden by the
422
+ // hasDisplayContent gate above.
423
+ attributes: (value: unknown) => {
424
+ if (!hasDisplayContent(value)) return null;
425
+ return JsonBlock(value);
426
+ },
460
427
  // Dates — wrapped with TimestampTooltip showing UTC/local + relative time
461
428
  createdAt: timestampWithTooltipOrNull,
462
429
  startedAt: timestampWithTooltipOrNull,
@@ -468,12 +435,18 @@ const attributeToDisplayFn: Record<
468
435
  // Resolved attributes, won't actually use this function
469
436
  metadata: (value: unknown) => {
470
437
  if (!hasDisplayContent(value)) return null;
471
- if (isEncryptedMarker(value)) return <EncryptedFieldBlock />;
438
+ if (isEncryptedMarker(value)) return <EncryptedDataBlock />;
472
439
  if (isExpiredMarker(value)) return <ExpiredFieldBlock />;
473
440
  return JsonBlock(value);
474
441
  },
475
442
  input: (value: unknown, context?: DisplayContext) => {
476
- if (isEncryptedMarker(value)) return <EncryptedFieldBlock />;
443
+ if (isEncryptedMarker(value)) {
444
+ return (
445
+ <DetailCard summary="Input">
446
+ <EncryptedFieldBlock />
447
+ </DetailCard>
448
+ );
449
+ }
477
450
  if (isExpiredMarker(value)) return <ExpiredFieldBlock />;
478
451
  // Check if input has args + closure vars structure
479
452
  if (value && typeof value === 'object' && 'args' in value) {
@@ -482,8 +455,6 @@ const attributeToDisplayFn: Record<
482
455
  closureVars?: Record<string, unknown>;
483
456
  thisVal?: unknown;
484
457
  };
485
- const argCount = Array.isArray(args) ? args.length : 0;
486
- const argLabel = argCount === 1 ? 'argument' : 'arguments';
487
458
  const hasClosureVars = hasDisplayContent(closureVars);
488
459
  const hasThisVal = hasDisplayContent(thisVal);
489
460
  const hasArgs = hasDisplayContent(args);
@@ -512,22 +483,12 @@ const attributeToDisplayFn: Record<
512
483
 
513
484
  // Don't render an empty "Input (0 arguments)" card when no input exists.
514
485
  if (!hasArgs && !hasClosureVars && !hasThisVal) {
515
- return (
516
- <DetailCard
517
- summary="Input (no data)"
518
- disabled
519
- summaryClassName="text-label-14 font-medium py-2"
520
- />
521
- );
486
+ return <DetailCard summary="Input (no data)" disabled />;
522
487
  }
523
488
 
524
489
  return (
525
490
  <>
526
- <DetailCard
527
- summary={`Input (${argCount} ${argLabel})`}
528
- summaryClassName="text-label-14 font-medium py-2"
529
- contentClassName="mt-0"
530
- >
491
+ <DetailCard summary="Input">
531
492
  {Array.isArray(args)
532
493
  ? args.map((v, i) => (
533
494
  <div className="mt-2 first:mt-0" key={i}>
@@ -542,30 +503,18 @@ const attributeToDisplayFn: Record<
542
503
  </DetailCard>
543
504
  )}
544
505
  {hasThisVal && (
545
- <DetailCard summary="this">{JsonBlock(thisVal)}</DetailCard>
506
+ <DetailCard summary="Context">{JsonBlock(thisVal)}</DetailCard>
546
507
  )}
547
508
  </>
548
509
  );
549
510
  }
550
511
 
551
512
  // Fallback: treat as plain array or object
552
- const argCount = Array.isArray(value) ? value.length : 0;
553
- const argLabel = argCount === 1 ? 'argument' : 'arguments';
554
513
  if (!hasDisplayContent(value)) {
555
- return (
556
- <DetailCard
557
- summary="Input (no data)"
558
- disabled
559
- summaryClassName="text-label-14 font-medium py-2"
560
- />
561
- );
514
+ return <DetailCard summary="Input (no data)" disabled />;
562
515
  }
563
516
  return (
564
- <DetailCard
565
- summary={`Input (${argCount} ${argLabel})`}
566
- summaryClassName="text-label-14 font-medium py-2"
567
- contentClassName="mt-0"
568
- >
517
+ <DetailCard summary="Input">
569
518
  {Array.isArray(value)
570
519
  ? value.map((v, i) => (
571
520
  <div className="mt-2 first:mt-0" key={i}>
@@ -577,21 +526,25 @@ const attributeToDisplayFn: Record<
577
526
  );
578
527
  },
579
528
  output: (value: unknown) => {
529
+ if (isEncryptedMarker(value)) {
530
+ return (
531
+ <DetailCard summary="Output">
532
+ <EncryptedFieldBlock />
533
+ </DetailCard>
534
+ );
535
+ }
580
536
  if (!hasDisplayContent(value)) return null;
581
- if (isEncryptedMarker(value)) return <EncryptedFieldBlock />;
582
537
  if (isExpiredMarker(value)) return <ExpiredFieldBlock />;
583
- return (
584
- <DetailCard
585
- summary="Output"
586
- summaryClassName="text-label-14 font-medium py-2"
587
- contentClassName="mt-0"
588
- >
589
- {JsonBlock(value)}
590
- </DetailCard>
591
- );
538
+ return <DetailCard summary="Output">{JsonBlock(value)}</DetailCard>;
592
539
  },
593
540
  error: (value: unknown) => {
594
- if (isEncryptedMarker(value)) return <EncryptedFieldBlock />;
541
+ if (isEncryptedMarker(value)) {
542
+ return (
543
+ <DetailCard summary="Error" defaultOpen>
544
+ <EncryptedFieldBlock />
545
+ </DetailCard>
546
+ );
547
+ }
595
548
  if (isExpiredMarker(value)) return <ExpiredFieldBlock />;
596
549
  if (!hasDisplayContent(value)) return null;
597
550
 
@@ -599,31 +552,33 @@ const attributeToDisplayFn: Record<
599
552
  // pre-formatted text. Otherwise fall back to the raw JSON viewer.
600
553
  if (isStructuredErrorWithStack(value)) {
601
554
  return (
602
- <DetailCard
603
- summary="Error"
604
- summaryClassName="text-label-14 font-medium py-2"
605
- contentClassName="mt-0"
606
- >
555
+ <DetailCard summary="Error" defaultOpen>
607
556
  <ErrorStackBlock value={value} />
608
557
  </DetailCard>
609
558
  );
610
559
  }
611
560
 
612
561
  return (
613
- <DetailCard
614
- summary="Error"
615
- summaryClassName="text-label-14 font-medium py-2"
616
- contentClassName="mt-0"
617
- >
562
+ <DetailCard summary="Error" defaultOpen>
618
563
  {JsonBlock(value)}
619
564
  </DetailCard>
620
565
  );
621
566
  },
622
567
  eventData: (value: unknown) => {
623
- if (isEncryptedMarker(value)) return <EncryptedFieldBlock />;
568
+ if (isEncryptedMarker(value)) {
569
+ return (
570
+ <DetailCard summary="Event Data" defaultOpen>
571
+ <EncryptedFieldBlock />
572
+ </DetailCard>
573
+ );
574
+ }
624
575
  if (isExpiredMarker(value)) return <ExpiredFieldBlock />;
625
576
  if (!hasDisplayContent(value)) return null;
626
- return <DetailCard summary="Event Data">{JsonBlock(value)}</DetailCard>;
577
+ return (
578
+ <DetailCard summary="Event Data" defaultOpen>
579
+ {JsonBlock(value)}
580
+ </DetailCard>
581
+ );
627
582
  },
628
583
  errorCode: (value: unknown) => {
629
584
  if (typeof value !== 'string' || value.length === 0) return null;
@@ -639,6 +594,15 @@ const resolvableAttributes = [
639
594
  'eventData',
640
595
  ];
641
596
 
597
+ // Attributes whose displayFn renders its own section header via DetailCard,
598
+ // so the outer AttributeBlock should not duplicate the label.
599
+ const selfHeaderedAttributes = new Set([
600
+ 'input',
601
+ 'output',
602
+ 'error',
603
+ 'eventData',
604
+ ]);
605
+
642
606
  const ExpiredDataMessage = () => (
643
607
  <div
644
608
  className="text-copy-12 rounded-md border p-4 my-2"
@@ -652,6 +616,13 @@ const ExpiredDataMessage = () => (
652
616
  </div>
653
617
  );
654
618
 
619
+ const copyableBasicAttributes = new Set<AttributeKey>([
620
+ 'stepId',
621
+ 'hookId',
622
+ 'eventId',
623
+ 'deploymentId',
624
+ ]);
625
+
655
626
  export const AttributeBlock = ({
656
627
  attribute,
657
628
  value,
@@ -665,19 +636,26 @@ export const AttributeBlock = ({
665
636
  inline?: boolean;
666
637
  context?: DisplayContext;
667
638
  }) => {
639
+ const decryptCtx = useContext(DecryptClickContext);
668
640
  const isExpandableLoadingTarget =
669
- attribute === 'input' || attribute === 'eventData';
641
+ attribute === 'input' ||
642
+ attribute === 'output' ||
643
+ attribute === 'eventData';
670
644
  if (isLoading && isExpandableLoadingTarget && !hasDisplayContent(value)) {
671
- return (
672
- <div
673
- className={`my-2 flex flex-col ${attribute === 'input' ? 'gap-2 my-3.5' : 'gap-0'}`}
674
- >
675
- <span className="text-label-14 text-gray-1000 font-medium first-letter:uppercase">
676
- {attribute}
677
- </span>
678
- <Skeleton className="h-9 w-full rounded-md" />
679
- </div>
680
- );
645
+ const label =
646
+ attribute === 'eventData'
647
+ ? 'Event Data'
648
+ : attribute === 'output'
649
+ ? 'Output'
650
+ : 'Input';
651
+ if (decryptCtx?.hasEncryptedData) {
652
+ return (
653
+ <DetailCard summary={label} defaultOpen={attribute === 'eventData'}>
654
+ <EncryptedFieldBlock />
655
+ </DetailCard>
656
+ );
657
+ }
658
+ return <DetailCard summary={label} />;
681
659
  }
682
660
 
683
661
  const displayFn =
@@ -706,6 +684,10 @@ export const AttributeBlock = ({
706
684
  );
707
685
  }
708
686
 
687
+ if (selfHeaderedAttributes.has(attribute)) {
688
+ return <>{displayValue}</>;
689
+ }
690
+
709
691
  return (
710
692
  <div className="relative">
711
693
  {typeof isLoading === 'boolean' && isLoading && (
@@ -716,10 +698,7 @@ export const AttributeBlock = ({
716
698
  />
717
699
  </div>
718
700
  )}
719
- <div
720
- key={attribute}
721
- className={`my-2 flex flex-col ${attribute === 'input' || attribute === 'output' || attribute === 'error' ? 'gap-2 my-3.5' : 'gap-0'}`}
722
- >
701
+ <div key={attribute} className="my-2 flex flex-col gap-0">
723
702
  <span className="text-label-14 text-gray-1000 font-medium first-letter:uppercase">
724
703
  {attribute}
725
704
  </span>
@@ -789,16 +768,18 @@ export const AttributePanel = ({
789
768
 
790
769
  if (!isLoading) return present;
791
770
 
792
- // During loading, ensure input appears so its skeleton renders
771
+ if (resource === 'sleep') return present;
772
+
773
+ // During loading, ensure sections appear so their skeletons render
793
774
  // in the correct position (above the events section).
794
- const loadingDefaults = ['input'];
775
+ const loadingDefaults = ['input', 'output'];
795
776
  for (const key of loadingDefaults) {
796
777
  if (!present.includes(key)) {
797
778
  present.push(key);
798
779
  }
799
780
  }
800
781
  return present.sort(sortByAttributeOrder);
801
- }, [displayData, isLoading]);
782
+ }, [displayData, isLoading, resource]);
802
783
 
803
784
  // Filter out attributes that return null
804
785
  const visibleBasicAttributes = basicAttributes.filter((attribute) => {
@@ -854,125 +835,121 @@ export const AttributePanel = ({
854
835
  });
855
836
  }, []);
856
837
 
838
+ const outerDecryptCtx = useContext(DecryptClickContext);
839
+ const decryptValue = onDecrypt
840
+ ? {
841
+ onDecrypt,
842
+ isDecrypting,
843
+ hasEncryptedData: outerDecryptCtx?.hasEncryptedData,
844
+ }
845
+ : outerDecryptCtx;
846
+
857
847
  return (
858
848
  <RunClickContext.Provider value={onRunClick}>
859
849
  <StreamClickContext.Provider value={onStreamClick}>
860
- <DecryptClickContext.Provider
861
- value={onDecrypt ? { onDecrypt, isDecrypting } : undefined}
862
- >
863
- <div>
864
- {/* Basic attributes in a vertical layout with border */}
865
- {visibleBasicAttributes.length > 0 && (
866
- <div
867
- className="mb-3 flex flex-col overflow-hidden rounded-md border"
868
- style={{
869
- borderColor: 'var(--ds-gray-300)',
870
- }}
871
- >
872
- {orderedBasicAttributes.map((attribute, index) => {
873
- const displayValue = attributeToDisplayFn[
874
- attribute as keyof typeof attributeToDisplayFn
875
- ]?.(displayData[attribute as keyof typeof displayData]);
876
- const isModuleSpecifier = attribute === 'moduleSpecifier';
877
- const moduleSpecifierValue =
878
- typeof displayValue === 'string'
879
- ? displayValue
880
- : String(
881
- displayValue ?? displayData.moduleSpecifier ?? ''
882
- );
883
- const shouldCapitalizeLabel =
884
- attribute !== 'workflowCoreVersion';
885
- const showResumeAtSkeleton =
886
- isLoading && resource === 'sleep' && !displayData.resumeAt;
887
- const showDivider =
888
- index < orderedBasicAttributes.length - 1 ||
889
- showResumeAtSkeleton;
890
-
891
- return (
892
- <div key={attribute} className="py-1">
893
- <div className="flex min-h-[32px] items-center justify-between gap-4 rounded-sm px-2.5 py-1">
894
- <span
895
- className={
896
- shouldCapitalizeLabel
897
- ? 'text-[14px] first-letter:uppercase'
898
- : 'text-[14px]'
899
- }
900
- style={{ color: 'var(--ds-gray-700)' }}
901
- >
902
- {getAttributeDisplayName(attribute)}
903
- </span>
904
- {isModuleSpecifier ? (
905
- <button
906
- type="button"
907
- className="min-w-0 max-w-[70%] truncate text-right text-[13px] font-mono"
908
- style={{
909
- color: 'var(--ds-gray-1000)',
910
- background: 'transparent',
911
- border: 'none',
912
- padding: 0,
913
- }}
914
- title={moduleSpecifierValue}
915
- onClick={() =>
916
- handleCopyModuleSpecifier(moduleSpecifierValue)
917
- }
918
- >
919
- {moduleSpecifierValue}
920
- </button>
921
- ) : (
922
- <span
923
- className="min-w-0 max-w-[70%] truncate text-right text-[13px] font-mono"
924
- style={{ color: 'var(--ds-gray-1000)' }}
925
- >
926
- {displayValue}
927
- </span>
928
- )}
929
- </div>
930
- {showDivider ? (
931
- <div
932
- className="mx-2.5 border-b"
933
- style={{ borderColor: 'var(--ds-gray-300)' }}
934
- />
935
- ) : null}
936
- </div>
937
- );
938
- })}
939
- {isLoading && resource === 'sleep' && !displayData.resumeAt && (
940
- <div className="py-1">
941
- <div className="flex min-h-[32px] items-center justify-between gap-4 rounded-sm px-2.5 py-1">
942
- <span
943
- className="text-[14px] first-letter:uppercase"
944
- style={{ color: 'var(--ds-gray-700)' }}
850
+ <DecryptClickContext.Provider value={decryptValue}>
851
+ {visibleBasicAttributes.length > 0 && (
852
+ <div className="flex flex-col overflow-hidden divide-y divide-gray-alpha-400 mb-3">
853
+ {orderedBasicAttributes.map((attribute) => {
854
+ const displayValue = attributeToDisplayFn[
855
+ attribute as keyof typeof attributeToDisplayFn
856
+ ]?.(displayData[attribute as keyof typeof displayData]);
857
+ const isModuleSpecifier = attribute === 'moduleSpecifier';
858
+ const isCopyableBasicAttribute =
859
+ copyableBasicAttributes.has(attribute as AttributeKey) &&
860
+ typeof displayValue === 'string';
861
+ const moduleSpecifierValue =
862
+ typeof displayValue === 'string'
863
+ ? displayValue
864
+ : String(displayValue ?? displayData.moduleSpecifier ?? '');
865
+
866
+ return (
867
+ <div
868
+ className="flex items-center justify-between py-2"
869
+ key={attribute}
870
+ >
871
+ <span className="text-label-14 text-gray-900">
872
+ {getAttributeDisplayName(attribute)}
873
+ </span>
874
+ {isModuleSpecifier ? (
875
+ <button
876
+ type="button"
877
+ className="min-w-0 max-w-[70%] truncate text-right text-label-13 font-mono"
878
+ style={{
879
+ color: 'var(--ds-gray-1000)',
880
+ background: 'transparent',
881
+ border: 'none',
882
+ padding: 0,
883
+ }}
884
+ title={moduleSpecifierValue}
885
+ onClick={() =>
886
+ handleCopyModuleSpecifier(moduleSpecifierValue)
887
+ }
945
888
  >
946
- resumeAt
889
+ {moduleSpecifierValue}
890
+ </button>
891
+ ) : isCopyableBasicAttribute ? (
892
+ <div
893
+ className="flex min-w-0 max-w-[70%] items-center justify-end gap-1 text-right text-[13px] font-mono"
894
+ style={{
895
+ color: 'var(--ds-gray-1000)',
896
+ }}
897
+ title={displayValue}
898
+ >
899
+ <MiddleTruncate
900
+ value={displayValue}
901
+ className="flex-1"
902
+ />
903
+ <CopyButton
904
+ copyText={displayValue}
905
+ ariaLabel={`Copy ${getAttributeDisplayName(attribute)}`}
906
+ className="shrink-0 -mr-1"
907
+ />
908
+ </div>
909
+ ) : (
910
+ <span className="text-right text-label-13 font-mono">
911
+ {displayValue}
947
912
  </span>
948
- <Skeleton className="h-4 w-[55%]" />
949
- </div>
913
+ )}
950
914
  </div>
951
- )}
952
- </div>
953
- )}
954
- {error ? (
955
- <ErrorCard
956
- title="Failed to load resource details"
957
- details={error.message}
958
- className="my-4"
959
- />
960
- ) : hasExpired ? (
961
- <ExpiredDataMessage />
962
- ) : (
963
- <>
964
- {resolvedAttributes.map((attribute) => (
965
- <AttributeBlock
966
- isLoading={isLoading}
967
- key={attribute}
968
- attribute={attribute}
969
- value={displayData[attribute as keyof typeof displayData]}
970
- context={displayContext}
971
- />
972
- ))}
973
- </>
974
- )}
975
- </div>
915
+ );
916
+ })}
917
+ {isLoading && resource === 'sleep' && !displayData.resumeAt && (
918
+ <div className="py-1">
919
+ <div className="flex min-h-[32px] items-center justify-between gap-4 rounded-sm px-2.5 py-1">
920
+ <span
921
+ className="text-[14px] first-letter:uppercase"
922
+ style={{ color: 'var(--ds-gray-700)' }}
923
+ >
924
+ resumeAt
925
+ </span>
926
+ <Skeleton className="h-4 w-[55%]" />
927
+ </div>
928
+ </div>
929
+ )}
930
+ </div>
931
+ )}
932
+ {error ? (
933
+ <ErrorCard
934
+ title="Failed to load resource details"
935
+ details={error.message}
936
+ className="my-4"
937
+ />
938
+ ) : hasExpired ? (
939
+ <ExpiredDataMessage />
940
+ ) : resolvedAttributes.length > 0 ? (
941
+ <>
942
+ {resolvedAttributes.map((attribute) => (
943
+ <AttributeBlock
944
+ isLoading={isLoading}
945
+ key={attribute}
946
+ attribute={attribute}
947
+ value={displayData[attribute as keyof typeof displayData]}
948
+ context={displayContext}
949
+ />
950
+ ))}
951
+ </>
952
+ ) : null}
976
953
  </DecryptClickContext.Provider>
977
954
  </StreamClickContext.Provider>
978
955
  </RunClickContext.Provider>