@workflow/web-shared 5.0.0-beta.4 → 5.0.0-beta.41
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 +5 -13
- package/dist/components/error-boundary.js +1 -1
- package/dist/components/event-list-view.d.ts +46 -1
- package/dist/components/event-list-view.d.ts.map +1 -1
- package/dist/components/event-list-view.js +235 -97
- package/dist/components/hook-actions.js +1 -1
- package/dist/components/index.d.ts +8 -5
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/index.js +6 -4
- package/dist/components/sidebar/attribute-panel.d.ts +4 -1
- package/dist/components/sidebar/attribute-panel.d.ts.map +1 -1
- package/dist/components/sidebar/attribute-panel.js +173 -119
- package/dist/components/sidebar/attributes-block.d.ts +40 -0
- package/dist/components/sidebar/attributes-block.d.ts.map +1 -0
- package/dist/components/sidebar/attributes-block.js +140 -0
- package/dist/components/sidebar/conversation-view.js +1 -1
- package/dist/components/sidebar/copyable-data-block.d.ts +2 -0
- package/dist/components/sidebar/copyable-data-block.d.ts.map +1 -1
- package/dist/components/sidebar/copyable-data-block.js +17 -16
- package/dist/components/sidebar/entity-detail-panel.d.ts +11 -11
- package/dist/components/sidebar/entity-detail-panel.d.ts.map +1 -1
- package/dist/components/sidebar/entity-detail-panel.js +37 -71
- package/dist/components/sidebar/events-list.d.ts +6 -2
- package/dist/components/sidebar/events-list.d.ts.map +1 -1
- package/dist/components/sidebar/events-list.js +77 -63
- package/dist/components/sidebar/resolve-hook-modal.js +1 -1
- package/dist/components/sidebar/sidebar-data-context.d.ts +10 -7
- package/dist/components/sidebar/sidebar-data-context.d.ts.map +1 -1
- package/dist/components/sidebar/sidebar-data-context.js +1 -1
- package/dist/components/sidebar/span-detail-merge.d.ts +31 -0
- package/dist/components/sidebar/span-detail-merge.d.ts.map +1 -0
- package/dist/components/sidebar/span-detail-merge.js +92 -0
- package/dist/components/sidebar/use-selected-span-detail.d.ts +16 -0
- package/dist/components/sidebar/use-selected-span-detail.d.ts.map +1 -0
- package/dist/components/sidebar/use-selected-span-detail.js +93 -0
- package/dist/components/stream-viewer-skeleton.d.ts +2 -0
- package/dist/components/stream-viewer-skeleton.d.ts.map +1 -0
- package/dist/components/stream-viewer-skeleton.js +12 -0
- package/dist/components/stream-viewer.d.ts +1 -1
- package/dist/components/stream-viewer.d.ts.map +1 -1
- package/dist/components/stream-viewer.js +9 -26
- package/dist/components/trace-viewer/components/copy-button.d.ts.map +1 -0
- package/dist/components/trace-viewer/components/copy-button.js +27 -0
- package/dist/components/trace-viewer/components/detail-panel-width.d.ts +34 -0
- package/dist/components/trace-viewer/components/detail-panel-width.d.ts.map +1 -0
- package/dist/components/trace-viewer/components/detail-panel-width.js +67 -0
- package/dist/components/trace-viewer/components/detail-panel.d.ts +14 -0
- package/dist/components/trace-viewer/components/detail-panel.d.ts.map +1 -0
- package/dist/components/trace-viewer/components/detail-panel.js +122 -0
- package/dist/components/trace-viewer/components/draggable-border.d.ts +31 -0
- package/dist/components/trace-viewer/components/draggable-border.d.ts.map +1 -0
- package/dist/components/trace-viewer/components/draggable-border.js +140 -0
- package/dist/components/trace-viewer/components/event-list.d.ts +10 -0
- package/dist/components/trace-viewer/components/event-list.d.ts.map +1 -0
- package/dist/components/trace-viewer/components/event-list.js +47 -0
- package/dist/components/trace-viewer/components/middle-truncate/copy-selection.d.ts.map +1 -0
- package/dist/components/trace-viewer/components/middle-truncate/copy-selection.js +54 -0
- package/dist/components/trace-viewer/components/middle-truncate/middle-truncate.d.ts.map +1 -0
- package/dist/components/trace-viewer/components/middle-truncate/middle-truncate.js +81 -0
- package/dist/components/{new-trace-viewer → trace-viewer}/components/middle-truncate/truncate.d.ts +1 -1
- package/dist/components/trace-viewer/components/middle-truncate/truncate.d.ts.map +1 -0
- package/dist/components/trace-viewer/components/middle-truncate/truncate.js +101 -0
- package/dist/components/trace-viewer/components/middle-truncate/use-middle-truncate.d.ts.map +1 -0
- package/dist/components/trace-viewer/components/middle-truncate/use-middle-truncate.js +114 -0
- package/dist/components/trace-viewer/components/minimap.d.ts +14 -0
- package/dist/components/trace-viewer/components/minimap.d.ts.map +1 -0
- package/dist/components/trace-viewer/components/minimap.js +337 -0
- package/dist/components/trace-viewer/components/pane-constants.d.ts +14 -0
- package/dist/components/trace-viewer/components/pane-constants.d.ts.map +1 -0
- package/dist/components/trace-viewer/components/pane-constants.js +16 -0
- package/dist/components/trace-viewer/components/span-markers.d.ts +46 -0
- package/dist/components/trace-viewer/components/span-markers.d.ts.map +1 -0
- package/dist/components/trace-viewer/components/span-markers.js +85 -0
- package/dist/components/trace-viewer/components/split-pane.d.ts +10 -0
- package/dist/components/trace-viewer/components/split-pane.d.ts.map +1 -0
- package/dist/components/trace-viewer/components/split-pane.js +41 -0
- package/dist/components/trace-viewer/components/timeline.d.ts +42 -0
- package/dist/components/trace-viewer/components/timeline.d.ts.map +1 -0
- package/dist/components/trace-viewer/components/timeline.js +321 -0
- package/dist/components/trace-viewer/components/timeline.module.css +46 -0
- package/dist/components/trace-viewer/components/trace-shortcut-helper.d.ts +5 -0
- package/dist/components/trace-viewer/components/trace-shortcut-helper.d.ts.map +1 -0
- package/dist/components/trace-viewer/components/trace-shortcut-helper.js +55 -0
- package/dist/components/trace-viewer/components/trace-shortcut-helper.module.css +20 -0
- package/dist/components/trace-viewer/components/trace-viewer-skeleton.d.ts +2 -0
- package/dist/components/trace-viewer/components/trace-viewer-skeleton.d.ts.map +1 -0
- package/dist/components/trace-viewer/components/trace-viewer-skeleton.js +26 -0
- package/dist/components/trace-viewer/components/use-element-width.d.ts +8 -0
- package/dist/components/trace-viewer/components/use-element-width.d.ts.map +1 -0
- package/dist/components/trace-viewer/components/use-element-width.js +25 -0
- package/dist/components/trace-viewer/components/use-row-window.d.ts +34 -0
- package/dist/components/trace-viewer/components/use-row-window.d.ts.map +1 -0
- package/dist/components/trace-viewer/components/use-row-window.js +107 -0
- package/dist/components/trace-viewer/context.d.ts +14 -182
- package/dist/components/trace-viewer/context.d.ts.map +1 -1
- package/dist/components/trace-viewer/context.js +44 -351
- package/dist/components/trace-viewer/icons.d.ts.map +1 -0
- package/dist/components/trace-viewer/icons.js +15 -0
- package/dist/components/trace-viewer/search.d.ts +19 -0
- package/dist/components/trace-viewer/search.d.ts.map +1 -0
- package/dist/components/trace-viewer/search.js +134 -0
- package/dist/components/trace-viewer/trace-viewer.d.ts +7 -28
- package/dist/components/trace-viewer/trace-viewer.d.ts.map +1 -1
- package/dist/components/trace-viewer/trace-viewer.js +306 -356
- package/dist/components/trace-viewer/types.d.ts +2 -211
- package/dist/components/trace-viewer/types.d.ts.map +1 -1
- package/dist/components/trace-viewer/types.js +1 -1
- package/dist/components/trace-viewer/util/timing.d.ts +2 -2
- package/dist/components/trace-viewer/util/timing.d.ts.map +1 -1
- package/dist/components/trace-viewer/util/timing.js +3 -3
- package/dist/components/trace-viewer/utils.d.ts +111 -0
- package/dist/components/trace-viewer/utils.d.ts.map +1 -0
- package/dist/components/trace-viewer/utils.js +430 -0
- package/dist/components/{trace-viewer-new.d.ts → trace-viewer.d.ts} +7 -3
- package/dist/components/trace-viewer.d.ts.map +1 -0
- package/dist/components/trace-viewer.js +21 -0
- package/dist/components/ui/alert.js +3 -3
- package/dist/components/ui/button.d.ts +10 -0
- package/dist/components/ui/button.d.ts.map +1 -0
- package/dist/components/ui/button.js +46 -0
- package/dist/components/ui/card.d.ts.map +1 -1
- package/dist/components/ui/card.js +4 -4
- package/dist/components/ui/collapsible.d.ts +46 -0
- package/dist/components/ui/collapsible.d.ts.map +1 -0
- package/dist/components/ui/collapsible.js +66 -0
- package/dist/components/ui/context-card.d.ts +47 -0
- package/dist/components/ui/context-card.d.ts.map +1 -0
- package/dist/components/ui/context-card.js +471 -0
- package/dist/components/ui/data-inspector.d.ts +7 -6
- package/dist/components/ui/data-inspector.d.ts.map +1 -1
- package/dist/components/ui/data-inspector.js +232 -99
- package/dist/components/ui/data-inspector.styles.d.ts +29 -0
- package/dist/components/ui/data-inspector.styles.d.ts.map +1 -0
- package/dist/components/ui/data-inspector.styles.js +95 -0
- package/dist/components/ui/decrypt-button.js +1 -1
- package/dist/components/ui/error-card.js +3 -3
- package/dist/components/ui/error-stack-block.d.ts +17 -10
- package/dist/components/ui/error-stack-block.d.ts.map +1 -1
- package/dist/components/ui/error-stack-block.js +49 -30
- package/dist/components/ui/icon-button.d.ts +19 -0
- package/dist/components/ui/icon-button.d.ts.map +1 -0
- package/dist/components/ui/icon-button.js +29 -0
- package/dist/components/ui/kbd.d.ts +10 -0
- package/dist/components/ui/kbd.d.ts.map +1 -0
- package/dist/components/ui/kbd.js +25 -0
- package/dist/components/ui/load-more-button.js +1 -1
- package/dist/components/ui/menu-dropdown.js +1 -1
- package/dist/components/ui/skeleton.js +2 -2
- package/dist/components/ui/spinner.js +1 -1
- package/dist/components/ui/timestamp-tooltip.d.ts +34 -1
- package/dist/components/ui/timestamp-tooltip.d.ts.map +1 -1
- package/dist/components/ui/timestamp-tooltip.js +80 -132
- package/dist/components/ui/tooltip.d.ts +10 -0
- package/dist/components/ui/tooltip.d.ts.map +1 -0
- package/dist/components/ui/tooltip.js +12 -0
- package/dist/components/workflow-traces/event-colors.d.ts +2 -1
- package/dist/components/workflow-traces/event-colors.d.ts.map +1 -1
- package/dist/components/workflow-traces/event-colors.js +15 -7
- package/dist/components/workflow-traces/trace-span-construction.d.ts +6 -3
- package/dist/components/workflow-traces/trace-span-construction.d.ts.map +1 -1
- package/dist/components/workflow-traces/trace-span-construction.js +71 -30
- package/dist/components/workflow-traces/trace-time-utils.js +1 -1
- package/dist/hooks/use-dark-mode.js +1 -1
- package/dist/hooks/use-intersection-observer.d.ts +17 -0
- package/dist/hooks/use-intersection-observer.d.ts.map +1 -0
- package/dist/hooks/use-intersection-observer.js +29 -0
- package/dist/hooks/use-load-more-on-scroll.d.ts +21 -0
- package/dist/hooks/use-load-more-on-scroll.d.ts.map +1 -0
- package/dist/hooks/use-load-more-on-scroll.js +28 -0
- package/dist/hooks/use-reduced-motion.d.ts +10 -0
- package/dist/hooks/use-reduced-motion.d.ts.map +1 -0
- package/dist/hooks/use-reduced-motion.js +29 -0
- package/dist/index.d.ts +4 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -3
- package/dist/lib/cn.d.ts +10 -0
- package/dist/lib/cn.d.ts.map +1 -0
- package/dist/lib/cn.js +83 -0
- package/dist/lib/event-analysis.d.ts +1 -1
- package/dist/lib/event-analysis.d.ts.map +1 -1
- package/dist/lib/event-analysis.js +3 -2
- package/dist/lib/event-materialization.d.ts +1 -1
- package/dist/lib/event-materialization.d.ts.map +1 -1
- package/dist/lib/event-materialization.js +29 -21
- package/dist/lib/exact-event-search-id.d.ts +25 -0
- package/dist/lib/exact-event-search-id.d.ts.map +1 -0
- package/dist/lib/exact-event-search-id.js +39 -0
- package/dist/lib/hydration.d.ts +17 -2
- package/dist/lib/hydration.d.ts.map +1 -1
- package/dist/lib/hydration.js +194 -23
- package/dist/lib/stream-display.js +1 -1
- package/dist/lib/toast.js +1 -1
- package/dist/lib/trace-builder.d.ts +17 -5
- package/dist/lib/trace-builder.d.ts.map +1 -1
- package/dist/lib/trace-builder.js +32 -14
- package/dist/lib/trace-types.d.ts +54 -0
- package/dist/lib/trace-types.d.ts.map +1 -0
- package/dist/lib/trace-types.js +2 -0
- package/dist/lib/utils.d.ts +14 -2
- package/dist/lib/utils.d.ts.map +1 -1
- package/dist/lib/utils.js +46 -6
- package/dist/lib/zstd-browser-decoder.d.ts +7 -0
- package/dist/lib/zstd-browser-decoder.d.ts.map +1 -0
- package/dist/lib/zstd-browser-decoder.js +41 -0
- package/dist/styles.css +210 -139
- package/package.json +11 -8
- package/src/components/event-list-view.tsx +385 -123
- package/src/components/index.ts +14 -8
- package/src/components/sidebar/attribute-panel.tsx +331 -282
- package/src/components/sidebar/attributes-block.tsx +326 -0
- package/src/components/sidebar/copyable-data-block.tsx +50 -28
- package/src/components/sidebar/entity-detail-panel.tsx +92 -126
- package/src/components/sidebar/events-list.tsx +197 -179
- package/src/components/sidebar/sidebar-data-context.tsx +11 -11
- package/src/components/sidebar/span-detail-merge.ts +134 -0
- package/src/components/sidebar/use-selected-span-detail.ts +146 -0
- package/src/components/stream-viewer-skeleton.tsx +34 -0
- package/src/components/stream-viewer.tsx +34 -106
- package/src/components/{new-trace-viewer → trace-viewer}/components/copy-button.tsx +2 -2
- package/src/components/trace-viewer/components/detail-panel-width.test.ts +123 -0
- package/src/components/trace-viewer/components/detail-panel-width.ts +76 -0
- package/src/components/trace-viewer/components/detail-panel.tsx +273 -0
- package/src/components/trace-viewer/components/draggable-border.tsx +216 -0
- package/src/components/trace-viewer/components/event-list.tsx +144 -0
- package/src/components/{new-trace-viewer → trace-viewer}/components/middle-truncate/middle-truncate.tsx +8 -8
- package/src/components/{new-trace-viewer → trace-viewer}/components/middle-truncate/truncate.ts +1 -1
- package/src/components/trace-viewer/components/minimap.tsx +556 -0
- package/src/components/trace-viewer/components/pane-constants.ts +19 -0
- package/src/components/trace-viewer/components/span-markers.tsx +173 -0
- package/src/components/trace-viewer/components/split-pane.tsx +141 -0
- package/src/components/trace-viewer/components/timeline.module.css +46 -0
- package/src/components/trace-viewer/components/timeline.tsx +850 -0
- package/src/components/trace-viewer/components/trace-shortcut-helper.module.css +20 -0
- package/src/components/trace-viewer/components/trace-shortcut-helper.tsx +116 -0
- package/src/components/trace-viewer/components/trace-viewer-skeleton.tsx +114 -0
- package/src/components/trace-viewer/components/use-element-width.ts +29 -0
- package/src/components/trace-viewer/components/use-row-window.ts +140 -0
- package/src/components/trace-viewer/context.tsx +63 -656
- package/src/components/{new-trace-viewer → trace-viewer}/icons.tsx +2 -2
- package/src/components/trace-viewer/search.ts +178 -0
- package/src/components/trace-viewer/trace-viewer.tsx +563 -531
- package/src/components/trace-viewer/types.ts +2 -234
- package/src/components/trace-viewer/util/timing.ts +2 -2
- package/src/components/trace-viewer/utils.test.ts +458 -0
- package/src/components/{new-trace-viewer → trace-viewer}/utils.ts +271 -50
- package/src/components/{trace-viewer-new.tsx → trace-viewer.tsx} +24 -18
- package/src/components/ui/alert.tsx +2 -2
- package/src/components/ui/button.tsx +68 -0
- package/src/components/ui/card.tsx +3 -10
- package/src/components/ui/collapsible.tsx +219 -0
- package/src/components/ui/context-card.tsx +784 -0
- package/src/components/ui/data-inspector.styles.ts +96 -0
- package/src/components/ui/data-inspector.tsx +501 -204
- package/src/components/ui/error-card.tsx +4 -4
- package/src/components/ui/error-stack-block.tsx +84 -55
- package/src/components/ui/icon-button.tsx +53 -0
- package/src/components/ui/kbd.tsx +43 -0
- package/src/components/ui/skeleton.tsx +1 -1
- package/src/components/ui/timestamp-tooltip.tsx +132 -194
- package/src/components/ui/tooltip.tsx +47 -0
- package/src/components/workflow-traces/event-colors.ts +15 -11
- package/src/components/workflow-traces/trace-span-construction.test.ts +85 -0
- package/src/components/workflow-traces/trace-span-construction.ts +97 -32
- package/src/hooks/use-intersection-observer.ts +57 -0
- package/src/hooks/use-load-more-on-scroll.ts +49 -0
- package/src/hooks/use-reduced-motion.ts +34 -0
- package/src/index.ts +10 -4
- package/src/lib/cn.ts +93 -0
- package/src/lib/event-analysis.ts +6 -4
- package/src/lib/event-materialization.ts +35 -21
- package/src/lib/exact-event-search-id.ts +67 -0
- package/src/lib/hydration.ts +210 -30
- package/src/lib/trace-builder.ts +43 -19
- package/src/lib/trace-types.ts +56 -0
- package/src/lib/utils.test.ts +33 -0
- package/src/lib/utils.ts +53 -6
- package/src/lib/zstd-browser-decoder.ts +42 -0
- package/src/styles.css +210 -139
- package/dist/components/error-boundary.js.map +0 -1
- package/dist/components/event-list-view.js.map +0 -1
- package/dist/components/hook-actions.js.map +0 -1
- package/dist/components/index.js.map +0 -1
- package/dist/components/new-trace-viewer/components/copy-button.d.ts.map +0 -1
- package/dist/components/new-trace-viewer/components/copy-button.js +0 -27
- package/dist/components/new-trace-viewer/components/copy-button.js.map +0 -1
- package/dist/components/new-trace-viewer/components/event-list.d.ts +0 -8
- package/dist/components/new-trace-viewer/components/event-list.d.ts.map +0 -1
- package/dist/components/new-trace-viewer/components/event-list.js +0 -38
- package/dist/components/new-trace-viewer/components/event-list.js.map +0 -1
- package/dist/components/new-trace-viewer/components/middle-truncate/copy-selection.d.ts.map +0 -1
- package/dist/components/new-trace-viewer/components/middle-truncate/copy-selection.js +0 -54
- package/dist/components/new-trace-viewer/components/middle-truncate/copy-selection.js.map +0 -1
- package/dist/components/new-trace-viewer/components/middle-truncate/middle-truncate.d.ts.map +0 -1
- package/dist/components/new-trace-viewer/components/middle-truncate/middle-truncate.js +0 -81
- package/dist/components/new-trace-viewer/components/middle-truncate/middle-truncate.js.map +0 -1
- package/dist/components/new-trace-viewer/components/middle-truncate/truncate.d.ts.map +0 -1
- package/dist/components/new-trace-viewer/components/middle-truncate/truncate.js +0 -101
- package/dist/components/new-trace-viewer/components/middle-truncate/truncate.js.map +0 -1
- package/dist/components/new-trace-viewer/components/middle-truncate/use-middle-truncate.d.ts.map +0 -1
- package/dist/components/new-trace-viewer/components/middle-truncate/use-middle-truncate.js +0 -114
- package/dist/components/new-trace-viewer/components/middle-truncate/use-middle-truncate.js.map +0 -1
- package/dist/components/new-trace-viewer/components/split-pane.d.ts +0 -14
- package/dist/components/new-trace-viewer/components/split-pane.d.ts.map +0 -1
- package/dist/components/new-trace-viewer/components/split-pane.js +0 -99
- package/dist/components/new-trace-viewer/components/split-pane.js.map +0 -1
- package/dist/components/new-trace-viewer/components/timeline.d.ts +0 -31
- package/dist/components/new-trace-viewer/components/timeline.d.ts.map +0 -1
- package/dist/components/new-trace-viewer/components/timeline.js +0 -138
- package/dist/components/new-trace-viewer/components/timeline.js.map +0 -1
- package/dist/components/new-trace-viewer/context.d.ts +0 -15
- package/dist/components/new-trace-viewer/context.d.ts.map +0 -1
- package/dist/components/new-trace-viewer/context.js +0 -47
- package/dist/components/new-trace-viewer/context.js.map +0 -1
- package/dist/components/new-trace-viewer/detail-panel.d.ts +0 -10
- package/dist/components/new-trace-viewer/detail-panel.d.ts.map +0 -1
- package/dist/components/new-trace-viewer/detail-panel.js +0 -12
- package/dist/components/new-trace-viewer/detail-panel.js.map +0 -1
- package/dist/components/new-trace-viewer/icons.d.ts.map +0 -1
- package/dist/components/new-trace-viewer/icons.js +0 -15
- package/dist/components/new-trace-viewer/icons.js.map +0 -1
- package/dist/components/new-trace-viewer/trace-viewer.d.ts +0 -8
- package/dist/components/new-trace-viewer/trace-viewer.d.ts.map +0 -1
- package/dist/components/new-trace-viewer/trace-viewer.js +0 -349
- package/dist/components/new-trace-viewer/trace-viewer.js.map +0 -1
- package/dist/components/new-trace-viewer/types.d.ts +0 -1
- package/dist/components/new-trace-viewer/types.d.ts.map +0 -1
- package/dist/components/new-trace-viewer/types.js +0 -2
- package/dist/components/new-trace-viewer/types.js.map +0 -1
- package/dist/components/new-trace-viewer/util/constants.d.ts +0 -1
- package/dist/components/new-trace-viewer/util/constants.d.ts.map +0 -1
- package/dist/components/new-trace-viewer/util/constants.js +0 -2
- package/dist/components/new-trace-viewer/util/constants.js.map +0 -1
- package/dist/components/new-trace-viewer/utils.d.ts +0 -42
- package/dist/components/new-trace-viewer/utils.d.ts.map +0 -1
- package/dist/components/new-trace-viewer/utils.js +0 -305
- package/dist/components/new-trace-viewer/utils.js.map +0 -1
- package/dist/components/run-trace-view.d.ts +0 -25
- package/dist/components/run-trace-view.d.ts.map +0 -1
- package/dist/components/run-trace-view.js +0 -11
- package/dist/components/run-trace-view.js.map +0 -1
- package/dist/components/sidebar/attribute-panel.js.map +0 -1
- package/dist/components/sidebar/conversation-view.js.map +0 -1
- package/dist/components/sidebar/copyable-data-block.js.map +0 -1
- package/dist/components/sidebar/detail-card.d.ts +0 -14
- package/dist/components/sidebar/detail-card.d.ts.map +0 -1
- package/dist/components/sidebar/detail-card.js +0 -20
- package/dist/components/sidebar/detail-card.js.map +0 -1
- package/dist/components/sidebar/entity-detail-panel.js.map +0 -1
- package/dist/components/sidebar/events-list.js.map +0 -1
- package/dist/components/sidebar/resolve-hook-modal.js.map +0 -1
- package/dist/components/sidebar/sidebar-data-context.js.map +0 -1
- package/dist/components/stream-viewer.js.map +0 -1
- package/dist/components/trace-viewer/components/map.d.ts +0 -8
- package/dist/components/trace-viewer/components/map.d.ts.map +0 -1
- package/dist/components/trace-viewer/components/map.js +0 -162
- package/dist/components/trace-viewer/components/map.js.map +0 -1
- package/dist/components/trace-viewer/components/markers.d.ts +0 -23
- package/dist/components/trace-viewer/components/markers.d.ts.map +0 -1
- package/dist/components/trace-viewer/components/markers.js +0 -416
- package/dist/components/trace-viewer/components/markers.js.map +0 -1
- package/dist/components/trace-viewer/components/node.d.ts +0 -35
- package/dist/components/trace-viewer/components/node.d.ts.map +0 -1
- package/dist/components/trace-viewer/components/node.js +0 -285
- package/dist/components/trace-viewer/components/node.js.map +0 -1
- package/dist/components/trace-viewer/components/search-input.d.ts +0 -9
- package/dist/components/trace-viewer/components/search-input.d.ts.map +0 -1
- package/dist/components/trace-viewer/components/search-input.js +0 -9
- package/dist/components/trace-viewer/components/search-input.js.map +0 -1
- package/dist/components/trace-viewer/components/search.d.ts +0 -3
- package/dist/components/trace-viewer/components/search.d.ts.map +0 -1
- package/dist/components/trace-viewer/components/search.js +0 -25
- package/dist/components/trace-viewer/components/search.js.map +0 -1
- package/dist/components/trace-viewer/components/span-content.d.ts +0 -19
- package/dist/components/trace-viewer/components/span-content.d.ts.map +0 -1
- package/dist/components/trace-viewer/components/span-content.js +0 -137
- package/dist/components/trace-viewer/components/span-content.js.map +0 -1
- package/dist/components/trace-viewer/components/span-detail-panel.d.ts +0 -10
- package/dist/components/trace-viewer/components/span-detail-panel.d.ts.map +0 -1
- package/dist/components/trace-viewer/components/span-detail-panel.js +0 -285
- package/dist/components/trace-viewer/components/span-detail-panel.js.map +0 -1
- package/dist/components/trace-viewer/components/span-segments.d.ts +0 -50
- package/dist/components/trace-viewer/components/span-segments.d.ts.map +0 -1
- package/dist/components/trace-viewer/components/span-segments.js +0 -445
- package/dist/components/trace-viewer/components/span-segments.js.map +0 -1
- package/dist/components/trace-viewer/components/span-strategies.d.ts +0 -46
- package/dist/components/trace-viewer/components/span-strategies.d.ts.map +0 -1
- package/dist/components/trace-viewer/components/span-strategies.js +0 -108
- package/dist/components/trace-viewer/components/span-strategies.js.map +0 -1
- package/dist/components/trace-viewer/components/ui.d.ts +0 -28
- package/dist/components/trace-viewer/components/ui.d.ts.map +0 -1
- package/dist/components/trace-viewer/components/ui.js +0 -39
- package/dist/components/trace-viewer/components/ui.js.map +0 -1
- package/dist/components/trace-viewer/components/zoom-button.d.ts +0 -3
- package/dist/components/trace-viewer/components/zoom-button.d.ts.map +0 -1
- package/dist/components/trace-viewer/components/zoom-button.js +0 -29
- package/dist/components/trace-viewer/components/zoom-button.js.map +0 -1
- package/dist/components/trace-viewer/components/zoom-icons.d.ts +0 -11
- package/dist/components/trace-viewer/components/zoom-icons.d.ts.map +0 -1
- package/dist/components/trace-viewer/components/zoom-icons.js +0 -11
- package/dist/components/trace-viewer/components/zoom-icons.js.map +0 -1
- package/dist/components/trace-viewer/context.js.map +0 -1
- package/dist/components/trace-viewer/index.d.ts +0 -5
- package/dist/components/trace-viewer/index.d.ts.map +0 -1
- package/dist/components/trace-viewer/index.js +0 -4
- package/dist/components/trace-viewer/index.js.map +0 -1
- package/dist/components/trace-viewer/trace-viewer.js.map +0 -1
- package/dist/components/trace-viewer/trace-viewer.module.css +0 -1465
- package/dist/components/trace-viewer/types.js.map +0 -1
- package/dist/components/trace-viewer/util/constants.d.ts +0 -9
- package/dist/components/trace-viewer/util/constants.d.ts.map +0 -1
- package/dist/components/trace-viewer/util/constants.js +0 -9
- package/dist/components/trace-viewer/util/constants.js.map +0 -1
- package/dist/components/trace-viewer/util/scrollbar-width.d.ts +0 -2
- package/dist/components/trace-viewer/util/scrollbar-width.d.ts.map +0 -1
- package/dist/components/trace-viewer/util/scrollbar-width.js +0 -14
- package/dist/components/trace-viewer/util/scrollbar-width.js.map +0 -1
- package/dist/components/trace-viewer/util/timing.js.map +0 -1
- package/dist/components/trace-viewer/util/tree.d.ts +0 -13
- package/dist/components/trace-viewer/util/tree.d.ts.map +0 -1
- package/dist/components/trace-viewer/util/tree.js +0 -227
- package/dist/components/trace-viewer/util/tree.js.map +0 -1
- package/dist/components/trace-viewer/util/use-immediate-style.d.ts +0 -15
- package/dist/components/trace-viewer/util/use-immediate-style.d.ts.map +0 -1
- package/dist/components/trace-viewer/util/use-immediate-style.js +0 -23
- package/dist/components/trace-viewer/util/use-immediate-style.js.map +0 -1
- package/dist/components/trace-viewer/util/use-streaming-spans.d.ts +0 -8
- package/dist/components/trace-viewer/util/use-streaming-spans.d.ts.map +0 -1
- package/dist/components/trace-viewer/util/use-streaming-spans.js +0 -345
- package/dist/components/trace-viewer/util/use-streaming-spans.js.map +0 -1
- package/dist/components/trace-viewer/util/use-trackpad-zoom.d.ts +0 -6
- package/dist/components/trace-viewer/util/use-trackpad-zoom.d.ts.map +0 -1
- package/dist/components/trace-viewer/util/use-trackpad-zoom.js +0 -39
- package/dist/components/trace-viewer/util/use-trackpad-zoom.js.map +0 -1
- package/dist/components/trace-viewer/worker.d.ts +0 -2
- package/dist/components/trace-viewer/worker.d.ts.map +0 -1
- package/dist/components/trace-viewer/worker.js +0 -109
- package/dist/components/trace-viewer/worker.js.map +0 -1
- package/dist/components/trace-viewer-new.d.ts.map +0 -1
- package/dist/components/trace-viewer-new.js +0 -23
- package/dist/components/trace-viewer-new.js.map +0 -1
- package/dist/components/ui/alert.js.map +0 -1
- package/dist/components/ui/card.js.map +0 -1
- package/dist/components/ui/data-inspector.js.map +0 -1
- package/dist/components/ui/decrypt-button.js.map +0 -1
- package/dist/components/ui/error-card.js.map +0 -1
- package/dist/components/ui/error-stack-block.js.map +0 -1
- package/dist/components/ui/inspector-theme.d.ts +0 -95
- package/dist/components/ui/inspector-theme.d.ts.map +0 -1
- package/dist/components/ui/inspector-theme.js +0 -82
- package/dist/components/ui/inspector-theme.js.map +0 -1
- package/dist/components/ui/load-more-button.js.map +0 -1
- package/dist/components/ui/menu-dropdown.js.map +0 -1
- package/dist/components/ui/skeleton.js.map +0 -1
- package/dist/components/ui/spinner.js.map +0 -1
- package/dist/components/ui/timestamp-tooltip.js.map +0 -1
- package/dist/components/workflow-trace-view.d.ts +0 -40
- package/dist/components/workflow-trace-view.d.ts.map +0 -1
- package/dist/components/workflow-trace-view.js +0 -695
- package/dist/components/workflow-trace-view.js.map +0 -1
- package/dist/components/workflow-traces/event-colors.js.map +0 -1
- package/dist/components/workflow-traces/trace-colors.d.ts +0 -15
- package/dist/components/workflow-traces/trace-colors.d.ts.map +0 -1
- package/dist/components/workflow-traces/trace-colors.js +0 -87
- package/dist/components/workflow-traces/trace-colors.js.map +0 -1
- package/dist/components/workflow-traces/trace-span-construction.js.map +0 -1
- package/dist/components/workflow-traces/trace-time-utils.js.map +0 -1
- package/dist/hooks/use-dark-mode.js.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/lib/event-analysis.js.map +0 -1
- package/dist/lib/event-materialization.js.map +0 -1
- package/dist/lib/hydration.js.map +0 -1
- package/dist/lib/stream-display.js.map +0 -1
- package/dist/lib/toast.js.map +0 -1
- package/dist/lib/trace-builder.js.map +0 -1
- package/dist/lib/utils.js.map +0 -1
- package/src/components/new-trace-viewer/components/event-list.tsx +0 -116
- package/src/components/new-trace-viewer/components/split-pane.tsx +0 -185
- package/src/components/new-trace-viewer/components/timeline.tsx +0 -382
- package/src/components/new-trace-viewer/context.tsx +0 -92
- package/src/components/new-trace-viewer/detail-panel.tsx +0 -58
- package/src/components/new-trace-viewer/trace-viewer.tsx +0 -645
- package/src/components/new-trace-viewer/types.ts +0 -0
- package/src/components/new-trace-viewer/util/constants.ts +0 -0
- package/src/components/run-trace-view.tsx +0 -84
- package/src/components/sidebar/detail-card.tsx +0 -65
- package/src/components/trace-viewer/components/map.tsx +0 -226
- package/src/components/trace-viewer/components/markers.tsx +0 -612
- package/src/components/trace-viewer/components/node.tsx +0 -505
- package/src/components/trace-viewer/components/search-input.tsx +0 -52
- package/src/components/trace-viewer/components/search.tsx +0 -47
- package/src/components/trace-viewer/components/span-content.tsx +0 -247
- package/src/components/trace-viewer/components/span-detail-panel.tsx +0 -655
- package/src/components/trace-viewer/components/span-segments.ts +0 -585
- package/src/components/trace-viewer/components/span-strategies.ts +0 -205
- package/src/components/trace-viewer/components/ui.tsx +0 -156
- package/src/components/trace-viewer/components/zoom-button.tsx +0 -61
- package/src/components/trace-viewer/components/zoom-icons.tsx +0 -65
- package/src/components/trace-viewer/index.tsx +0 -4
- package/src/components/trace-viewer/trace-viewer.module.css +0 -1465
- package/src/components/trace-viewer/util/constants.ts +0 -8
- package/src/components/trace-viewer/util/scrollbar-width.ts +0 -13
- package/src/components/trace-viewer/util/tree.ts +0 -277
- package/src/components/trace-viewer/util/use-immediate-style.ts +0 -38
- package/src/components/trace-viewer/util/use-streaming-spans.ts +0 -426
- package/src/components/trace-viewer/util/use-trackpad-zoom.tsx +0 -51
- package/src/components/trace-viewer/worker.ts +0 -131
- package/src/components/ui/inspector-theme.ts +0 -110
- package/src/components/workflow-trace-view.tsx +0 -1171
- package/src/components/workflow-traces/trace-colors.ts +0 -112
- /package/dist/components/{new-trace-viewer → trace-viewer}/components/copy-button.d.ts +0 -0
- /package/dist/components/{new-trace-viewer → trace-viewer}/components/middle-truncate/copy-selection.d.ts +0 -0
- /package/dist/components/{new-trace-viewer → trace-viewer}/components/middle-truncate/middle-truncate.d.ts +0 -0
- /package/dist/components/{new-trace-viewer → trace-viewer}/components/middle-truncate/use-middle-truncate.d.ts +0 -0
- /package/dist/components/{new-trace-viewer → trace-viewer}/icons.d.ts +0 -0
- /package/src/components/{new-trace-viewer → trace-viewer}/components/middle-truncate/copy-selection.ts +0 -0
- /package/src/components/{new-trace-viewer → trace-viewer}/components/middle-truncate/use-middle-truncate.ts +0 -0
- /package/src/{components/trace-viewer/modules.d.ts → modules.d.ts} +0 -0
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import type { Event } from '@workflow/world';
|
|
2
|
+
|
|
3
|
+
const WORKFLOW_ULID_BODY = '[0123456789ABCDEFGHJKMNPQRSTVWXYZ]{26}';
|
|
4
|
+
|
|
5
|
+
const STEP_ID_PATTERN = new RegExp(`^step_(${WORKFLOW_ULID_BODY})$`, 'i');
|
|
6
|
+
const WAIT_ID_PATTERN = new RegExp(`^wait_(${WORKFLOW_ULID_BODY})$`, 'i');
|
|
7
|
+
const HOOK_ID_PATTERN = new RegExp(`^hook_(${WORKFLOW_ULID_BODY})$`, 'i');
|
|
8
|
+
const EVENT_ID_PATTERN = new RegExp(`^evnt_(${WORKFLOW_ULID_BODY})$`, 'i');
|
|
9
|
+
|
|
10
|
+
const WORKFLOW_ID_PREFIX_PATTERN = /^(step_|wait_|hook_|evnt_|wrun_)/i;
|
|
11
|
+
|
|
12
|
+
export type ExactWorkflowSearchIdKind = 'step' | 'wait' | 'hook' | 'event';
|
|
13
|
+
|
|
14
|
+
export type ExactWorkflowSearchId = {
|
|
15
|
+
kind: ExactWorkflowSearchIdKind;
|
|
16
|
+
id: string;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export type ExactIdSearchResult =
|
|
20
|
+
| { status: 'ok'; events: Event[]; truncated?: boolean }
|
|
21
|
+
| { status: 'not_found' }
|
|
22
|
+
| { status: 'error'; message: string };
|
|
23
|
+
|
|
24
|
+
function matchPrefixedId(
|
|
25
|
+
pattern: RegExp,
|
|
26
|
+
prefix: 'step' | 'wait' | 'hook' | 'evnt',
|
|
27
|
+
kind: ExactWorkflowSearchIdKind,
|
|
28
|
+
query: string
|
|
29
|
+
): ExactWorkflowSearchId | null {
|
|
30
|
+
const match = query.match(pattern);
|
|
31
|
+
if (!match) {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
return { kind, id: `${prefix}_${match[1].toUpperCase()}` };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Returns a parsed workflow ID when `query` is a full step, wait, hook, or event ID.
|
|
39
|
+
* Partial IDs and run IDs (`wrun_`) are ignored. ULID bodies are matched case-insensitively
|
|
40
|
+
* and normalized to uppercase in the returned ID.
|
|
41
|
+
*/
|
|
42
|
+
export function parseExactWorkflowSearchId(
|
|
43
|
+
query: string
|
|
44
|
+
): ExactWorkflowSearchId | null {
|
|
45
|
+
const trimmed = query.trim();
|
|
46
|
+
if (!trimmed) {
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return (
|
|
51
|
+
matchPrefixedId(STEP_ID_PATTERN, 'step', 'step', trimmed) ??
|
|
52
|
+
matchPrefixedId(WAIT_ID_PATTERN, 'wait', 'wait', trimmed) ??
|
|
53
|
+
matchPrefixedId(HOOK_ID_PATTERN, 'hook', 'hook', trimmed) ??
|
|
54
|
+
matchPrefixedId(EVENT_ID_PATTERN, 'evnt', 'event', trimmed)
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** True when input looks like the user is attempting an ID search (including partial). */
|
|
59
|
+
export function looksLikeWorkflowIdSearchInput(query: string): boolean {
|
|
60
|
+
const trimmed = query.trim();
|
|
61
|
+
if (!WORKFLOW_ID_PREFIX_PATTERN.test(trimmed)) {
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
// Distinguish IDs (contain digits) from event-type strings like step_started.
|
|
65
|
+
// Assumes workflow event types do not include digits in their names.
|
|
66
|
+
return /\d/.test(trimmed);
|
|
67
|
+
}
|
package/src/lib/hydration.ts
CHANGED
|
@@ -16,7 +16,7 @@ import {
|
|
|
16
16
|
type Revivers,
|
|
17
17
|
serializedInstanceToRef,
|
|
18
18
|
} from '@workflow/core/serialization-format';
|
|
19
|
-
import {
|
|
19
|
+
import { getEventDataRefFields } from '@workflow/world';
|
|
20
20
|
|
|
21
21
|
// Re-export types and utilities that consumers need
|
|
22
22
|
export {
|
|
@@ -58,12 +58,60 @@ function base64ToArrayBuffer(base64: string): ArrayBuffer {
|
|
|
58
58
|
// Web revivers (browser-safe, no Buffer dependency)
|
|
59
59
|
// ---------------------------------------------------------------------------
|
|
60
60
|
|
|
61
|
+
/**
|
|
62
|
+
* Build a reviver for one of the built-in `Error` subclasses (e.g.
|
|
63
|
+
* `TypeError`, `RangeError`). The constructor for the named subclass is
|
|
64
|
+
* resolved off `globalThis` at call time so the produced instance has the
|
|
65
|
+
* correct prototype chain in the consumer realm. Falls back to a generic
|
|
66
|
+
* `Error` (with `name` set) if the global isn't available, which keeps the
|
|
67
|
+
* o11y UI rendering even on exotic browsers.
|
|
68
|
+
*
|
|
69
|
+
* `cause` is passed through `ErrorOptions` to the constructor when present,
|
|
70
|
+
* matching `getCommonRevivers` in `@workflow/core` so the resulting `cause`
|
|
71
|
+
* property has the same semantics (non-enumerable, set by the engine) as a
|
|
72
|
+
* freshly thrown Error in the consumer realm. The `'cause' in value` check
|
|
73
|
+
* preserves the distinction between "no cause" and "cause is undefined".
|
|
74
|
+
*/
|
|
75
|
+
function makeWebErrorSubclassReviver(
|
|
76
|
+
name:
|
|
77
|
+
| 'EvalError'
|
|
78
|
+
| 'RangeError'
|
|
79
|
+
| 'ReferenceError'
|
|
80
|
+
| 'SyntaxError'
|
|
81
|
+
| 'TypeError'
|
|
82
|
+
| 'URIError'
|
|
83
|
+
) {
|
|
84
|
+
return (value: { message: string; stack?: string; cause?: unknown }) => {
|
|
85
|
+
const opts = 'cause' in value ? { cause: value.cause } : undefined;
|
|
86
|
+
const Ctor = (globalThis as Record<string, any>)[name] as
|
|
87
|
+
| ErrorConstructor
|
|
88
|
+
| undefined;
|
|
89
|
+
let error: Error;
|
|
90
|
+
if (typeof Ctor === 'function') {
|
|
91
|
+
error = new Ctor(value.message, opts);
|
|
92
|
+
} else {
|
|
93
|
+
// Fallback path: no built-in subclass available (exotic env). Construct
|
|
94
|
+
// a plain Error with the right `name` and copy `cause` manually since
|
|
95
|
+
// the base Error constructor is what we actually called.
|
|
96
|
+
error = Object.assign(new Error(value.message, opts), { name });
|
|
97
|
+
}
|
|
98
|
+
if (value.stack !== undefined) error.stack = value.stack;
|
|
99
|
+
return error;
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
61
103
|
/**
|
|
62
104
|
* Get the web-specific revivers for hydrating serialized data.
|
|
63
105
|
*
|
|
64
106
|
* Uses `atob()` for base64 decoding (no Node.js Buffer dependency).
|
|
65
107
|
* All types are revived as real instances (Date, Map, Set, URL,
|
|
66
108
|
* URLSearchParams, Headers, Error, etc.).
|
|
109
|
+
*
|
|
110
|
+
* NOTE: this set must mirror the keys in `SerializableSpecial` (see
|
|
111
|
+
* `@workflow/core/serialization/types`). Any reducer key added on the
|
|
112
|
+
* serialization side that isn't covered here will cause `devalue.unflatten`
|
|
113
|
+
* to throw `Unknown type X`, which `hydrateResourceIO` swallows and
|
|
114
|
+
* surfaces as a "Failed to load resource details" banner in the o11y UI.
|
|
67
115
|
*/
|
|
68
116
|
export function getWebRevivers(): Revivers {
|
|
69
117
|
function reviveArrayBuffer(value: string): ArrayBuffer {
|
|
@@ -83,10 +131,120 @@ export function getWebRevivers(): Revivers {
|
|
|
83
131
|
BigUint64Array: (value: string) =>
|
|
84
132
|
new BigUint64Array(reviveArrayBuffer(value)),
|
|
85
133
|
Date: (value) => new Date(value),
|
|
134
|
+
|
|
135
|
+
// Error family. The reducer side (see
|
|
136
|
+
// `packages/core/src/serialization/reducers/common.ts`) emits a tagged
|
|
137
|
+
// entry for each built-in Error subclass plus the workflow-specific
|
|
138
|
+
// `FatalError` / `RetryableError` / `HookConflictError` /
|
|
139
|
+
// `RuntimeDecryptionError` and `AggregateError`. Without
|
|
140
|
+
// matching revivers here, `devalue.unflatten` throws "Unknown type X"
|
|
141
|
+
// — which surfaces in the web o11y UI as "Failed to load resource
|
|
142
|
+
// details: Unknown type FatalError".
|
|
86
143
|
Error: (value) => {
|
|
144
|
+
const opts = 'cause' in value ? { cause: value.cause } : undefined;
|
|
145
|
+
const error = new Error(value.message, opts);
|
|
146
|
+
error.name = value.name;
|
|
147
|
+
if (value.stack !== undefined) error.stack = value.stack;
|
|
148
|
+
return error;
|
|
149
|
+
},
|
|
150
|
+
EvalError: makeWebErrorSubclassReviver('EvalError'),
|
|
151
|
+
RangeError: makeWebErrorSubclassReviver('RangeError'),
|
|
152
|
+
ReferenceError: makeWebErrorSubclassReviver('ReferenceError'),
|
|
153
|
+
SyntaxError: makeWebErrorSubclassReviver('SyntaxError'),
|
|
154
|
+
TypeError: makeWebErrorSubclassReviver('TypeError'),
|
|
155
|
+
URIError: makeWebErrorSubclassReviver('URIError'),
|
|
156
|
+
AggregateError: (value) => {
|
|
157
|
+
const opts = 'cause' in value ? { cause: value.cause } : undefined;
|
|
158
|
+
const Ctor = (
|
|
159
|
+
globalThis as { AggregateError?: AggregateErrorConstructor }
|
|
160
|
+
).AggregateError;
|
|
161
|
+
const error =
|
|
162
|
+
typeof Ctor === 'function'
|
|
163
|
+
? new Ctor(value.errors, value.message, opts)
|
|
164
|
+
: Object.assign(new Error(value.message, opts), {
|
|
165
|
+
name: 'AggregateError',
|
|
166
|
+
errors: value.errors,
|
|
167
|
+
});
|
|
168
|
+
if (value.stack !== undefined) error.stack = value.stack;
|
|
169
|
+
return error;
|
|
170
|
+
},
|
|
171
|
+
// `FatalError` and `RetryableError` are not built-in browser globals,
|
|
172
|
+
// so we can't resolve a constructor from globalThis. The web o11y UI
|
|
173
|
+
// doesn't need `instanceof FatalError` to pass (no user code runs
|
|
174
|
+
// here) — it just needs `name`, `message`, `stack`, and any extra
|
|
175
|
+
// enumerable fields to render. Construct a plain `Error` with `name`
|
|
176
|
+
// set; ObjectInspector reads `constructor.name` for the displayed
|
|
177
|
+
// class label, but we don't have the real class, so we emit a tagged
|
|
178
|
+
// Error whose `name` field carries the class identity. This matches
|
|
179
|
+
// how the existing base `Error` reviver presents unknown subclasses.
|
|
180
|
+
FatalError: (value) => {
|
|
181
|
+
const opts = 'cause' in value ? { cause: value.cause } : undefined;
|
|
182
|
+
const error = new Error(value.message, opts);
|
|
183
|
+
error.name = 'FatalError';
|
|
184
|
+
if (value.stack !== undefined) error.stack = value.stack;
|
|
185
|
+
return error;
|
|
186
|
+
},
|
|
187
|
+
HookConflictError: (value) => {
|
|
188
|
+
const opts = 'cause' in value ? { cause: value.cause } : undefined;
|
|
189
|
+
const error = new Error(value.message, opts) as Error & {
|
|
190
|
+
token?: string;
|
|
191
|
+
conflictingRunId?: string;
|
|
192
|
+
};
|
|
193
|
+
error.name = 'HookConflictError';
|
|
194
|
+
error.token = value.token;
|
|
195
|
+
if (value.conflictingRunId !== undefined) {
|
|
196
|
+
error.conflictingRunId = value.conflictingRunId;
|
|
197
|
+
}
|
|
198
|
+
if (value.stack !== undefined) error.stack = value.stack;
|
|
199
|
+
return error;
|
|
200
|
+
},
|
|
201
|
+
RetryableError: (value) => {
|
|
202
|
+
const opts = 'cause' in value ? { cause: value.cause } : undefined;
|
|
203
|
+
const error = new Error(value.message, opts) as Error & {
|
|
204
|
+
retryAfter?: Date;
|
|
205
|
+
};
|
|
206
|
+
error.name = 'RetryableError';
|
|
207
|
+
if (value.stack !== undefined) error.stack = value.stack;
|
|
208
|
+
// `retryAfter` is serialized as an epoch ms number (see the runtime
|
|
209
|
+
// RetryableError reducer for the rationale around realm-safety).
|
|
210
|
+
// Rehydrate as a Date so o11y consumers can render it directly.
|
|
211
|
+
// Guard against payloads from older runtime versions that predate
|
|
212
|
+
// the field — without this check, `new Date(undefined)` would
|
|
213
|
+
// produce an Invalid Date rather than omitting the property.
|
|
214
|
+
if (value.retryAfter != null) {
|
|
215
|
+
error.retryAfter = new Date(value.retryAfter);
|
|
216
|
+
}
|
|
217
|
+
return error;
|
|
218
|
+
},
|
|
219
|
+
RuntimeDecryptionError: (value) => {
|
|
220
|
+
const opts = 'cause' in value ? { cause: value.cause } : undefined;
|
|
221
|
+
const error = new Error(value.message, opts) as Error & {
|
|
222
|
+
context?: unknown;
|
|
223
|
+
};
|
|
224
|
+
error.name = 'RuntimeDecryptionError';
|
|
225
|
+
if (value.context !== undefined) {
|
|
226
|
+
error.context = value.context;
|
|
227
|
+
}
|
|
228
|
+
if (value.stack !== undefined) error.stack = value.stack;
|
|
229
|
+
return error;
|
|
230
|
+
},
|
|
231
|
+
DOMException: (value) => {
|
|
232
|
+
// Modern browsers and Node 18+ expose `DOMException` on globalThis.
|
|
233
|
+
// `AbortController.abort()` with no argument synthesizes one as the
|
|
234
|
+
// signal's reason, so this is a common payload for any aborted step.
|
|
235
|
+
const G = globalThis as { DOMException?: typeof DOMException };
|
|
236
|
+
if (typeof G.DOMException === 'function') {
|
|
237
|
+
const e = new G.DOMException(value.message, value.name);
|
|
238
|
+
if (value.stack !== undefined) e.stack = value.stack;
|
|
239
|
+
if ('cause' in value) (e as { cause?: unknown }).cause = value.cause;
|
|
240
|
+
return e;
|
|
241
|
+
}
|
|
87
242
|
const error = new Error(value.message);
|
|
88
243
|
error.name = value.name;
|
|
89
|
-
error.stack = value.stack;
|
|
244
|
+
if (value.stack !== undefined) error.stack = value.stack;
|
|
245
|
+
if ('cause' in value) {
|
|
246
|
+
(error as Error & { cause?: unknown }).cause = value.cause;
|
|
247
|
+
}
|
|
90
248
|
return error;
|
|
91
249
|
},
|
|
92
250
|
Float32Array: (value: string) => new Float32Array(reviveArrayBuffer(value)),
|
|
@@ -140,7 +298,7 @@ export function getWebRevivers(): Revivers {
|
|
|
140
298
|
|
|
141
299
|
// Web-specific overrides for class instances.
|
|
142
300
|
// Create objects with a dynamically-named constructor so that
|
|
143
|
-
//
|
|
301
|
+
// the data inspector shows the class name (it reads constructor.name).
|
|
144
302
|
Class: (value) => `<class:${extractClassName(value.classId)}>`,
|
|
145
303
|
Instance: (value) => {
|
|
146
304
|
// Run instances are rendered as clickable RunRef badges
|
|
@@ -153,7 +311,7 @@ export function getWebRevivers(): Revivers {
|
|
|
153
311
|
const props =
|
|
154
312
|
data && typeof data === 'object' ? { ...data } : { value: data };
|
|
155
313
|
// Create a constructor with the right name using computed property
|
|
156
|
-
// so
|
|
314
|
+
// so the data inspector's `object.constructor.name` shows the class name.
|
|
157
315
|
// Must use `function` (not arrow) because arrow functions have no .prototype.
|
|
158
316
|
// biome-ignore lint/complexity/useArrowFunction: arrow functions have no .prototype
|
|
159
317
|
const ctor = { [className]: function () {} }[className]!;
|
|
@@ -288,7 +446,7 @@ function replaceEncryptedAndExpiredWithMarkers<T>(resource: T): T {
|
|
|
288
446
|
if (result.eventData && typeof result.eventData === 'object') {
|
|
289
447
|
const eventType =
|
|
290
448
|
typeof result.eventType === 'string' ? result.eventType : '';
|
|
291
|
-
const refKeys =
|
|
449
|
+
const refKeys = getEventDataRefFields(eventType);
|
|
292
450
|
const ed = { ...(result.eventData as Record<string, unknown>) };
|
|
293
451
|
for (const key of refKeys) {
|
|
294
452
|
if (key in ed) {
|
|
@@ -307,36 +465,62 @@ function replaceEncryptedAndExpiredWithMarkers<T>(resource: T): T {
|
|
|
307
465
|
* When a key is provided, encrypted fields are decrypted before hydration.
|
|
308
466
|
* This is the async version used when the user clicks "Decrypt" in the web UI.
|
|
309
467
|
*
|
|
310
|
-
* Handles both top-level fields (input, output, metadata) and nested
|
|
311
|
-
*
|
|
468
|
+
* Handles both top-level fields (input, output, metadata) and nested eventData
|
|
469
|
+
* payload fields for that event type.
|
|
312
470
|
*/
|
|
313
471
|
export async function hydrateResourceIOWithKey<T>(
|
|
314
472
|
resource: T,
|
|
315
473
|
key: Uint8Array
|
|
316
474
|
): Promise<T> {
|
|
317
|
-
|
|
475
|
+
return hydrateResourceIOAsync(resource, key);
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
/**
|
|
479
|
+
* Async hydration for web display.
|
|
480
|
+
*
|
|
481
|
+
* This follows the same resource-field mapping as {@link hydrateResourceIO},
|
|
482
|
+
* but can also inflate compressed browser payloads through the registered
|
|
483
|
+
* zstd WASM decoder. When a key is provided, encrypted fields are decrypted
|
|
484
|
+
* first and then inflated/hydrated.
|
|
485
|
+
*/
|
|
486
|
+
export async function hydrateResourceIOAsync<T>(
|
|
487
|
+
resource: T,
|
|
488
|
+
key?: Uint8Array
|
|
489
|
+
): Promise<T> {
|
|
490
|
+
const { hydrateDataWithKey, deriveRunPayloadKeys } = await import(
|
|
318
491
|
'@workflow/core/serialization-format'
|
|
319
492
|
);
|
|
320
|
-
|
|
321
|
-
|
|
493
|
+
// Payloads may be zstd-compressed (the Web DecompressionStream has no zstd);
|
|
494
|
+
// register the WASM-backed browser decoder before hydrating. Idempotent and
|
|
495
|
+
// lazy — the WASM is only compiled when a zstd payload is actually decoded.
|
|
496
|
+
const { ensureZstdDecoderRegistered } = await import(
|
|
497
|
+
'./zstd-browser-decoder.js'
|
|
498
|
+
);
|
|
499
|
+
ensureZstdDecoderRegistered();
|
|
500
|
+
// Resolve the *full* key capability, not just the symmetric key: a run's
|
|
501
|
+
// event log can contain sealed ('encp') payloads that another run wrote to
|
|
502
|
+
// it (a cross-deployment hook resumption, say), and opening those needs the
|
|
503
|
+
// run's X25519 scalar in addition to its AES key. Both are derived from the
|
|
504
|
+
// same 32 bytes the key-retrieval endpoint returns.
|
|
505
|
+
// Resolve the *full* key capability, not just the symmetric key: a run's
|
|
506
|
+
// event log can contain sealed ('encp') payloads that another run wrote to
|
|
507
|
+
// it (a cross-deployment hook resumption, say), and opening those needs the
|
|
508
|
+
// run's X25519 scalar in addition to its AES key. Both derive from the same
|
|
509
|
+
// 32 bytes the key-retrieval endpoint returns.
|
|
510
|
+
const cryptoKey = key ? await deriveRunPayloadKeys(key) : undefined;
|
|
322
511
|
const revivers = getRevivers();
|
|
323
512
|
|
|
324
|
-
|
|
325
|
-
async function decryptField(
|
|
326
|
-
value: unknown,
|
|
327
|
-
rev: Revivers,
|
|
328
|
-
k: Awaited<ReturnType<typeof importKey>>
|
|
329
|
-
): Promise<unknown> {
|
|
513
|
+
async function hydrateField(value: unknown): Promise<unknown> {
|
|
330
514
|
// Already-hydrated: encrypted marker with stored bytes
|
|
331
515
|
if (isEncryptedMarker(value)) {
|
|
332
516
|
const raw = (value as any).__encryptedData as Uint8Array;
|
|
333
|
-
return hydrateDataWithKey(raw,
|
|
517
|
+
return cryptoKey ? hydrateDataWithKey(raw, revivers, cryptoKey) : value;
|
|
334
518
|
}
|
|
335
|
-
// Raw encrypted
|
|
519
|
+
// Raw Uint8Array: may be encrypted, compressed, or plain devalue.
|
|
336
520
|
if (value instanceof Uint8Array) {
|
|
337
|
-
return hydrateDataWithKey(value,
|
|
521
|
+
return hydrateDataWithKey(value, revivers, cryptoKey);
|
|
338
522
|
}
|
|
339
|
-
// Not
|
|
523
|
+
// Not serialized — return as-is.
|
|
340
524
|
return value;
|
|
341
525
|
}
|
|
342
526
|
|
|
@@ -346,29 +530,25 @@ export async function hydrateResourceIOWithKey<T>(
|
|
|
346
530
|
// Decrypt + hydrate top-level serialized fields (runs, steps, hooks)
|
|
347
531
|
for (const field of ['input', 'output', 'metadata', 'error']) {
|
|
348
532
|
if (field in result) {
|
|
349
|
-
result[field] = await
|
|
533
|
+
result[field] = await hydrateField(result[field]);
|
|
350
534
|
}
|
|
351
535
|
}
|
|
352
536
|
|
|
353
|
-
//
|
|
537
|
+
// Hydrate eventData subfields (events)
|
|
354
538
|
if (result.eventData && typeof result.eventData === 'object') {
|
|
355
539
|
const eventType =
|
|
356
540
|
typeof result.eventType === 'string' ? result.eventType : '';
|
|
357
|
-
const refKeys =
|
|
541
|
+
const refKeys = getEventDataRefFields(eventType);
|
|
358
542
|
const eventData = { ...(result.eventData as Record<string, unknown>) };
|
|
359
543
|
for (const field of refKeys) {
|
|
360
544
|
if (field in eventData) {
|
|
361
|
-
eventData[field] = await
|
|
362
|
-
eventData[field],
|
|
363
|
-
revivers,
|
|
364
|
-
cryptoKey
|
|
365
|
-
);
|
|
545
|
+
eventData[field] = await hydrateField(eventData[field]);
|
|
366
546
|
}
|
|
367
547
|
}
|
|
368
548
|
result.eventData = eventData;
|
|
369
549
|
}
|
|
370
550
|
|
|
371
|
-
return result as T;
|
|
551
|
+
return replaceEncryptedAndExpiredWithMarkers(result as T);
|
|
372
552
|
}
|
|
373
553
|
|
|
374
554
|
/**
|
|
@@ -387,7 +567,7 @@ export function hasEncryptedFields(resource: unknown): boolean {
|
|
|
387
567
|
|
|
388
568
|
if (r.eventData && typeof r.eventData === 'object') {
|
|
389
569
|
const eventType = typeof r.eventType === 'string' ? r.eventType : '';
|
|
390
|
-
const refKeys =
|
|
570
|
+
const refKeys = getEventDataRefFields(eventType);
|
|
391
571
|
const ed = r.eventData as Record<string, unknown>;
|
|
392
572
|
for (const key of refKeys) {
|
|
393
573
|
if (key in ed && isEncryptedMarker(ed[key])) return true;
|
package/src/lib/trace-builder.ts
CHANGED
|
@@ -6,30 +6,54 @@
|
|
|
6
6
|
* a fully-formed Trace ready for the trace viewer.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import type { Event, WorkflowRun } from '@workflow/world';
|
|
10
|
-
import type { Span } from '../components/trace-viewer/types';
|
|
11
9
|
import {
|
|
10
|
+
type Event,
|
|
11
|
+
isHookLifecycleEventType,
|
|
12
|
+
isStepEventType,
|
|
13
|
+
isWaitEventType,
|
|
14
|
+
type WorkflowRun,
|
|
15
|
+
} from '@workflow/world';
|
|
16
|
+
import {
|
|
17
|
+
getEventTimestamp,
|
|
12
18
|
hookToSpan,
|
|
13
19
|
runToSpan,
|
|
14
20
|
stepToSpan,
|
|
15
|
-
waitToSpan,
|
|
16
21
|
WORKFLOW_LIBRARY,
|
|
22
|
+
waitToSpan,
|
|
17
23
|
} from '../components/workflow-traces/trace-span-construction';
|
|
18
24
|
import { otelTimeToMs } from '../components/workflow-traces/trace-time-utils';
|
|
25
|
+
import type { Span } from './trace-types';
|
|
19
26
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
27
|
+
/**
|
|
28
|
+
* Events that belong to the run root span rather than a child entity span.
|
|
29
|
+
* Mirrors the fallthrough logic in {@link groupEventsByCorrelation}: events
|
|
30
|
+
* without a correlationId (run lifecycle) plus correlated events that aren't
|
|
31
|
+
* step/timer/hook events (e.g. `attr_set`, whose correlationId is a dedup
|
|
32
|
+
* token rather than a child entity ID).
|
|
33
|
+
*/
|
|
34
|
+
export const isRunLevelEvent = (event: Event): boolean =>
|
|
35
|
+
!event.correlationId ||
|
|
36
|
+
(!isWaitEventType(event.eventType) &&
|
|
37
|
+
!isHookLifecycleEventType(event.eventType) &&
|
|
38
|
+
!isStepEventType(event.eventType));
|
|
28
39
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
40
|
+
/**
|
|
41
|
+
* Filter the raw event list down to the events for a selected span.
|
|
42
|
+
* For the run root span this returns run-level events (see
|
|
43
|
+
* {@link isRunLevelEvent}); for child spans it returns the events
|
|
44
|
+
* correlated to that span's ID.
|
|
45
|
+
*/
|
|
46
|
+
export function filterSpanRawEvents(
|
|
47
|
+
events: Event[],
|
|
48
|
+
resource: string | undefined,
|
|
49
|
+
spanId: string | undefined
|
|
50
|
+
): Event[] {
|
|
51
|
+
if (resource === 'run') {
|
|
52
|
+
return events.filter(isRunLevelEvent);
|
|
53
|
+
}
|
|
54
|
+
if (!spanId) return [];
|
|
55
|
+
return events.filter((e) => e.correlationId === spanId);
|
|
56
|
+
}
|
|
33
57
|
|
|
34
58
|
// ---------------------------------------------------------------------------
|
|
35
59
|
// Event grouping
|
|
@@ -68,17 +92,17 @@ export function groupEventsByCorrelation(events: Event[]): GroupedEvents {
|
|
|
68
92
|
continue;
|
|
69
93
|
}
|
|
70
94
|
|
|
71
|
-
if (
|
|
95
|
+
if (isWaitEventType(event.eventType)) {
|
|
72
96
|
pushEvent(timerEvents, correlationId, event);
|
|
73
97
|
continue;
|
|
74
98
|
}
|
|
75
99
|
|
|
76
|
-
if (
|
|
100
|
+
if (isHookLifecycleEventType(event.eventType)) {
|
|
77
101
|
pushEvent(hookEvents, correlationId, event);
|
|
78
102
|
continue;
|
|
79
103
|
}
|
|
80
104
|
|
|
81
|
-
if (
|
|
105
|
+
if (isStepEventType(event.eventType)) {
|
|
82
106
|
pushEvent(eventsByStepId, correlationId, event);
|
|
83
107
|
continue;
|
|
84
108
|
}
|
|
@@ -101,7 +125,7 @@ export function groupEventsByCorrelation(events: Event[]): GroupedEvents {
|
|
|
101
125
|
function computeLatestKnownTime(events: Event[], run: WorkflowRun): Date {
|
|
102
126
|
let latest = new Date(run.createdAt).getTime();
|
|
103
127
|
for (const event of events) {
|
|
104
|
-
const t = new Date(event.createdAt).getTime();
|
|
128
|
+
const t = (getEventTimestamp(event) ?? new Date(event.createdAt)).getTime();
|
|
105
129
|
if (t > latest) latest = t;
|
|
106
130
|
}
|
|
107
131
|
return new Date(latest);
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
export interface Span {
|
|
2
|
+
name: string;
|
|
3
|
+
kind: number;
|
|
4
|
+
resource: string;
|
|
5
|
+
library: {
|
|
6
|
+
name: string;
|
|
7
|
+
version?: string;
|
|
8
|
+
};
|
|
9
|
+
spanId: string;
|
|
10
|
+
parentSpanId?: string;
|
|
11
|
+
status: {
|
|
12
|
+
code: number;
|
|
13
|
+
};
|
|
14
|
+
traceFlags: number;
|
|
15
|
+
attributes: Record<string, unknown>;
|
|
16
|
+
links: Record<string, unknown>[];
|
|
17
|
+
events: SpanEvent[];
|
|
18
|
+
startTime: [number, number];
|
|
19
|
+
endTime: [number, number];
|
|
20
|
+
duration: [number, number];
|
|
21
|
+
/**
|
|
22
|
+
* The time when the span became active/started executing (optional).
|
|
23
|
+
* If provided and different from startTime, the portion between startTime
|
|
24
|
+
* and activeStartTime will be rendered as a "queued" period with different styling.
|
|
25
|
+
*/
|
|
26
|
+
activeStartTime?: [number, number];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface SpanEvent {
|
|
30
|
+
name: string;
|
|
31
|
+
timestamp: [number, number];
|
|
32
|
+
attributes: Record<string, unknown>;
|
|
33
|
+
/**
|
|
34
|
+
* Optional custom color for this event marker (workflow-specific feature).
|
|
35
|
+
* If provided, this color will be used for the event marker line/diamond.
|
|
36
|
+
*/
|
|
37
|
+
color?: string;
|
|
38
|
+
/**
|
|
39
|
+
* Whether to show a vertical line for this event in the timeline (workflow-specific feature).
|
|
40
|
+
* If false, only the diamond marker on the span will be shown.
|
|
41
|
+
* Defaults to true if not specified.
|
|
42
|
+
*/
|
|
43
|
+
showVerticalLine?: boolean;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface Resource {
|
|
47
|
+
name: string;
|
|
48
|
+
attributes: Record<string, string>;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface Trace {
|
|
52
|
+
traceId: string;
|
|
53
|
+
resources?: Resource[];
|
|
54
|
+
spans: Span[];
|
|
55
|
+
rootSpanId?: string;
|
|
56
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { formatDurationPrecise } from './utils';
|
|
3
|
+
|
|
4
|
+
describe('formatDurationPrecise', () => {
|
|
5
|
+
it('shows whole milliseconds below 1s', () => {
|
|
6
|
+
expect(formatDurationPrecise(0)).toBe('0s');
|
|
7
|
+
expect(formatDurationPrecise(626)).toBe('626ms');
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
it('keeps sub-second precision instead of rounding up to a whole second', () => {
|
|
11
|
+
// The bug this guards: 1626ms must not read as "2s".
|
|
12
|
+
expect(formatDurationPrecise(1626)).toBe('1.63s');
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it('trims trailing zeros so whole/half seconds read cleanly', () => {
|
|
16
|
+
expect(formatDurationPrecise(2000)).toBe('2s');
|
|
17
|
+
expect(formatDurationPrecise(1500)).toBe('1.5s');
|
|
18
|
+
expect(formatDurationPrecise(1600)).toBe('1.6s');
|
|
19
|
+
expect(formatDurationPrecise(45_000)).toBe('45s');
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('honors a custom fraction-digit count (used by the timeline ruler)', () => {
|
|
23
|
+
// Fewer digits also coarsen the rounding: 1 digit rounds to 100ms.
|
|
24
|
+
expect(formatDurationPrecise(1500, 1)).toBe('1.5s');
|
|
25
|
+
expect(formatDurationPrecise(1620, 1)).toBe('1.6s');
|
|
26
|
+
expect(formatDurationPrecise(2000, 1)).toBe('2s');
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('decomposes durations of a minute or more', () => {
|
|
30
|
+
expect(formatDurationPrecise(65_000)).toBe('1m 5s');
|
|
31
|
+
expect(formatDurationPrecise(63_450)).toBe('1m 3.45s');
|
|
32
|
+
});
|
|
33
|
+
});
|
package/src/lib/utils.ts
CHANGED
|
@@ -1,11 +1,5 @@
|
|
|
1
1
|
import type { Step } from '@workflow/world';
|
|
2
2
|
import type { ModelMessage } from 'ai';
|
|
3
|
-
import { type ClassValue, clsx } from 'clsx';
|
|
4
|
-
import { twMerge } from 'tailwind-merge';
|
|
5
|
-
|
|
6
|
-
export function cn(...inputs: ClassValue[]) {
|
|
7
|
-
return twMerge(clsx(inputs));
|
|
8
|
-
}
|
|
9
3
|
|
|
10
4
|
const MS_IN_SECOND = 1000;
|
|
11
5
|
const MS_IN_MINUTE = 60 * MS_IN_SECOND;
|
|
@@ -80,6 +74,59 @@ export function formatDuration(ms: number, compact = false): string {
|
|
|
80
74
|
return parts.join(' ');
|
|
81
75
|
}
|
|
82
76
|
|
|
77
|
+
/**
|
|
78
|
+
* Formats a duration in milliseconds without snapping to whole seconds.
|
|
79
|
+
*
|
|
80
|
+
* Unlike {@link formatDuration}, this keeps sub-second detail, so 1626ms
|
|
81
|
+
* renders as "1.63s" rather than "2s". `fractionDigits` sets both the number of
|
|
82
|
+
* decimals on the seconds component (trailing zeros trimmed, so 2000ms is "2s"
|
|
83
|
+
* and 1500ms is "1.5s") and the rounding granularity — the default of 2 rounds
|
|
84
|
+
* to 10ms; the timeline ruler passes fewer digits to match its tick step.
|
|
85
|
+
*
|
|
86
|
+
* - < 1s: shows whole milliseconds (e.g. "626ms")
|
|
87
|
+
* - 1s – 1m: shows seconds with trimmed decimals (e.g. "1.63s", "1.5s", "45s")
|
|
88
|
+
* - >= 1m: decomposes into d/h/m with trimmed-decimal seconds (e.g. "1m 13.45s")
|
|
89
|
+
*/
|
|
90
|
+
export function formatDurationPrecise(ms: number, fractionDigits = 2): string {
|
|
91
|
+
if (ms === 0) {
|
|
92
|
+
return '0s';
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (ms < MS_IN_SECOND) {
|
|
96
|
+
const roundedMs = Math.round(ms);
|
|
97
|
+
if (roundedMs < MS_IN_SECOND) {
|
|
98
|
+
return `${roundedMs}ms`;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const granularityMs = MS_IN_SECOND / 10 ** fractionDigits;
|
|
103
|
+
const normalizedMs = Math.round(ms / granularityMs) * granularityMs;
|
|
104
|
+
|
|
105
|
+
if (normalizedMs < MS_IN_MINUTE) {
|
|
106
|
+
return `${Number((normalizedMs / MS_IN_SECOND).toFixed(fractionDigits))}s`;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const days = Math.floor(normalizedMs / MS_IN_DAY);
|
|
110
|
+
const hours = Math.floor((normalizedMs % MS_IN_DAY) / MS_IN_HOUR);
|
|
111
|
+
const minutes = Math.floor((normalizedMs % MS_IN_HOUR) / MS_IN_MINUTE);
|
|
112
|
+
const seconds = (normalizedMs % MS_IN_MINUTE) / MS_IN_SECOND;
|
|
113
|
+
|
|
114
|
+
const parts: string[] = [];
|
|
115
|
+
|
|
116
|
+
if (days > 0) {
|
|
117
|
+
parts.push(`${days}d`);
|
|
118
|
+
}
|
|
119
|
+
if (hours > 0) {
|
|
120
|
+
parts.push(`${hours}h`);
|
|
121
|
+
}
|
|
122
|
+
if (minutes > 0) {
|
|
123
|
+
parts.push(`${minutes}m`);
|
|
124
|
+
}
|
|
125
|
+
parts.push(`${Number(seconds.toFixed(fractionDigits))}s`);
|
|
126
|
+
|
|
127
|
+
return parts.join(' ');
|
|
128
|
+
}
|
|
129
|
+
|
|
83
130
|
/**
|
|
84
131
|
* Returns a formatted pagination display string
|
|
85
132
|
* @param currentPage - The current page number
|