@workflow/web-shared 5.0.0-beta.43 → 5.0.0-beta.46

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 (106) hide show
  1. package/README.md +4 -4
  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 +41 -20
  5. package/dist/components/sidebar/attribute-panel.d.ts +6 -2
  6. package/dist/components/sidebar/attribute-panel.d.ts.map +1 -1
  7. package/dist/components/sidebar/attribute-panel.js +10 -8
  8. package/dist/components/sidebar/copyable-data-block.d.ts.map +1 -1
  9. package/dist/components/sidebar/copyable-data-block.js +2 -2
  10. package/dist/components/sidebar/entity-detail-panel.d.ts +6 -2
  11. package/dist/components/sidebar/entity-detail-panel.d.ts.map +1 -1
  12. package/dist/components/sidebar/entity-detail-panel.js +13 -5
  13. package/dist/components/sidebar/events-list.d.ts.map +1 -1
  14. package/dist/components/sidebar/events-list.js +12 -5
  15. package/dist/components/sidebar/resolve-hook-modal.js +1 -1
  16. package/dist/components/sidebar/sidebar-data-context.d.ts +2 -0
  17. package/dist/components/sidebar/sidebar-data-context.d.ts.map +1 -1
  18. package/dist/components/sidebar/sidebar-data-context.js +1 -1
  19. package/dist/components/stream-viewer.js +2 -2
  20. package/dist/components/trace-viewer/components/detail-panel-width.d.ts +4 -4
  21. package/dist/components/trace-viewer/components/detail-panel-width.d.ts.map +1 -1
  22. package/dist/components/trace-viewer/components/detail-panel-width.js +5 -5
  23. package/dist/components/trace-viewer/components/detail-panel.d.ts +1 -1
  24. package/dist/components/trace-viewer/components/detail-panel.d.ts.map +1 -1
  25. package/dist/components/trace-viewer/components/detail-panel.js +3 -3
  26. package/dist/components/trace-viewer/components/draggable-border.d.ts +1 -1
  27. package/dist/components/trace-viewer/components/draggable-border.js +2 -2
  28. package/dist/components/trace-viewer/components/minimap.js +4 -4
  29. package/dist/components/trace-viewer/components/span-markers.d.ts +8 -9
  30. package/dist/components/trace-viewer/components/span-markers.d.ts.map +1 -1
  31. package/dist/components/trace-viewer/components/span-markers.js +13 -10
  32. package/dist/components/trace-viewer/components/timeline.d.ts +1 -1
  33. package/dist/components/trace-viewer/components/timeline.js +4 -4
  34. package/dist/components/trace-viewer/components/use-row-window.d.ts +1 -1
  35. package/dist/components/trace-viewer/components/use-row-window.js +2 -2
  36. package/dist/components/trace-viewer/trace-viewer.js +3 -3
  37. package/dist/components/trace-viewer/utils.d.ts +3 -1
  38. package/dist/components/trace-viewer/utils.d.ts.map +1 -1
  39. package/dist/components/trace-viewer/utils.js +6 -5
  40. package/dist/components/ui/data-inspector.d.ts +7 -1
  41. package/dist/components/ui/data-inspector.d.ts.map +1 -1
  42. package/dist/components/ui/data-inspector.js +10 -5
  43. package/dist/components/ui/decrypt-button.d.ts +5 -1
  44. package/dist/components/ui/decrypt-button.d.ts.map +1 -1
  45. package/dist/components/ui/decrypt-button.js +4 -4
  46. package/dist/components/ui/duplicate-event-tooltip.d.ts +15 -5
  47. package/dist/components/ui/duplicate-event-tooltip.d.ts.map +1 -1
  48. package/dist/components/ui/duplicate-event-tooltip.js +18 -9
  49. package/dist/components/ui/error-stack-block.js +4 -4
  50. package/dist/components/ui/timestamp-tooltip.d.ts +5 -3
  51. package/dist/components/ui/timestamp-tooltip.d.ts.map +1 -1
  52. package/dist/components/ui/timestamp-tooltip.js +9 -9
  53. package/dist/components/workflow-traces/event-colors.d.ts.map +1 -1
  54. package/dist/components/workflow-traces/event-colors.js +11 -1
  55. package/dist/components/workflow-traces/trace-span-construction.d.ts.map +1 -1
  56. package/dist/components/workflow-traces/trace-span-construction.js +5 -4
  57. package/dist/index.d.ts +1 -0
  58. package/dist/index.d.ts.map +1 -1
  59. package/dist/index.js +2 -1
  60. package/dist/lib/duplicate-events.d.ts +1 -1
  61. package/dist/lib/duplicate-events.js +2 -2
  62. package/dist/lib/hydration.d.ts.map +1 -1
  63. package/dist/lib/hydration.js +8 -13
  64. package/dist/lib/sealed-events.d.ts +28 -0
  65. package/dist/lib/sealed-events.d.ts.map +1 -0
  66. package/dist/lib/sealed-events.js +30 -0
  67. package/dist/lib/trace-builder.d.ts.map +1 -1
  68. package/dist/lib/trace-builder.js +8 -5
  69. package/dist/lib/utils.d.ts +1 -1
  70. package/dist/lib/utils.js +2 -2
  71. package/dist/lib/zstd-browser-decoder.js +2 -2
  72. package/package.json +5 -5
  73. package/src/components/event-list-view.tsx +57 -22
  74. package/src/components/sidebar/attribute-panel.tsx +15 -7
  75. package/src/components/sidebar/copyable-data-block.tsx +8 -1
  76. package/src/components/sidebar/entity-detail-panel.tsx +18 -2
  77. package/src/components/sidebar/events-list.tsx +16 -6
  78. package/src/components/sidebar/resolve-hook-modal.tsx +2 -2
  79. package/src/components/sidebar/sidebar-data-context.tsx +2 -0
  80. package/src/components/stream-viewer.tsx +1 -1
  81. package/src/components/trace-viewer/components/detail-panel-width.ts +4 -4
  82. package/src/components/trace-viewer/components/detail-panel.tsx +3 -1
  83. package/src/components/trace-viewer/components/draggable-border.tsx +1 -1
  84. package/src/components/trace-viewer/components/minimap.tsx +3 -3
  85. package/src/components/trace-viewer/components/span-markers.tsx +18 -13
  86. package/src/components/trace-viewer/components/timeline.tsx +4 -4
  87. package/src/components/trace-viewer/components/use-row-window.ts +1 -1
  88. package/src/components/trace-viewer/trace-viewer.tsx +2 -2
  89. package/src/components/trace-viewer/utils.test.ts +5 -3
  90. package/src/components/trace-viewer/utils.ts +12 -8
  91. package/src/components/ui/data-inspector.tsx +19 -3
  92. package/src/components/ui/decrypt-button.tsx +9 -2
  93. package/src/components/ui/duplicate-event-tooltip.tsx +32 -10
  94. package/src/components/ui/error-stack-block.tsx +3 -3
  95. package/src/components/ui/timestamp-tooltip.tsx +16 -6
  96. package/src/components/workflow-traces/event-colors.ts +11 -0
  97. package/src/components/workflow-traces/trace-span-construction.test.ts +27 -1
  98. package/src/components/workflow-traces/trace-span-construction.ts +5 -3
  99. package/src/index.ts +4 -0
  100. package/src/lib/duplicate-events.ts +1 -1
  101. package/src/lib/hydration.ts +7 -12
  102. package/src/lib/sealed-events.ts +32 -0
  103. package/src/lib/trace-builder.test.ts +29 -1
  104. package/src/lib/trace-builder.ts +10 -5
  105. package/src/lib/utils.ts +1 -1
  106. package/src/lib/zstd-browser-decoder.ts +1 -1
@@ -8,7 +8,7 @@
8
8
  *
9
9
  * The package leaves WASM sourcing to the caller; we resolve the shipped
10
10
  * `zstd.wasm` as a bundler asset (`new URL(..., import.meta.url)`, the same
11
- * pattern the trace-viewer Worker uses) and compile it once, lazily the
11
+ * pattern the trace-viewer Worker uses) and compile it once, lazily: the
12
12
  * WASM is fetched only the first time a zstd payload is actually decoded.
13
13
  */
14
14
  import { registerZstdDecoder } from '@workflow/core/serialization-format';
@@ -38,4 +38,4 @@ export function ensureZstdDecoderRegistered() {
38
38
  return decompressBytes(wasmModule, payload);
39
39
  });
40
40
  }
41
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoienN0ZC1icm93c2VyLWRlY29kZXIuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvbGliL3pzdGQtYnJvd3Nlci1kZWNvZGVyLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOzs7Ozs7Ozs7Ozs7R0FZRztBQUNILE9BQU8sRUFBRSxtQkFBbUIsRUFBRSxNQUFNLHFDQUFxQyxDQUFDO0FBRTFFLElBQUksVUFBVSxHQUFHLEtBQUssQ0FBQztBQUN2QixJQUFJLGFBQXNELENBQUM7QUFFM0QsU0FBUyxjQUFjO0lBQ3JCLElBQUksQ0FBQyxhQUFhLEVBQUUsQ0FBQztRQUNuQixNQUFNLEdBQUcsR0FBRyxJQUFJLEdBQUcsQ0FBQyxrQ0FBa0MsRUFBRSxNQUFNLENBQUMsSUFBSSxDQUFDLEdBQUcsQ0FBQyxDQUFDO1FBQ3pFLGFBQWEsR0FBRyxLQUFLLENBQUMsR0FBRyxDQUFDO2FBQ3ZCLElBQUksQ0FBQyxDQUFDLEdBQUcsRUFBRSxFQUFFLENBQUMsR0FBRyxDQUFDLFdBQVcsRUFBRSxDQUFDO2FBQ2hDLElBQUksQ0FBQyxDQUFDLEtBQUssRUFBRSxFQUFFLENBQUMsV0FBVyxDQUFDLE9BQU8sQ0FBQyxLQUFLLENBQUMsQ0FBQyxDQUFDO0lBQ2pELENBQUM7SUFDRCxPQUFPLGFBQWEsQ0FBQztBQUN2QixDQUFDO0FBRUQ7Ozs7R0FJRztBQUNILE1BQU0sVUFBVSwyQkFBMkI7SUFDekMsSUFBSSxVQUFVO1FBQUUsT0FBTztJQUN2QixVQUFVLEdBQUcsSUFBSSxDQUFDO0lBQ2xCLG1CQUFtQixDQUFDLEtBQUssRUFBRSxPQUFPLEVBQUUsRUFBRTtRQUNwQyxNQUFNLEVBQUUsZUFBZSxFQUFFLEdBQUcsTUFBTSxNQUFNLENBQUMsd0JBQXdCLENBQUMsQ0FBQztRQUNuRSxNQUFNLFVBQVUsR0FBRyxNQUFNLGNBQWMsRUFBRSxDQUFDO1FBQzFDLE9BQU8sZUFBZSxDQUFDLFVBQVUsRUFBRSxPQUFPLENBQUMsQ0FBQztJQUM5QyxDQUFDLENBQUMsQ0FBQztBQUNMLENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyIvKipcbiAqIEJyb3dzZXIgenN0ZCBkZWNvZGVyIGZvciB0aGUgbzExeSByZWFkIHBhdGguXG4gKlxuICogVGhlIFdlYiBgRGVjb21wcmVzc2lvblN0cmVhbWAgaGFzIG5vIHpzdGQgc3VwcG9ydCwgc28gYEB3b3JrZmxvdy9jb3JlYCdzXG4gKiBgaHlkcmF0ZURhdGFXaXRoS2V5YCBkZWxlZ2F0ZXMgenN0ZCBpbmZsYXRpb24gdG8gYSBkZWNvZGVyIHJlZ2lzdGVyZWQgdmlhXG4gKiBgcmVnaXN0ZXJac3RkRGVjb2RlcmAuIFRoaXMgbW9kdWxlIHN1cHBsaWVzIHRoYXQgZGVjb2RlciwgYmFja2VkIGJ5IHRoZVxuICogYEB0b290YWxsbmF0ZS96c3RkLXdhc21gIHNpbmdsZS1maWxlIFdBU00gZGVjb2Rlci5cbiAqXG4gKiBUaGUgcGFja2FnZSBsZWF2ZXMgV0FTTSBzb3VyY2luZyB0byB0aGUgY2FsbGVyOyB3ZSByZXNvbHZlIHRoZSBzaGlwcGVkXG4gKiBgenN0ZC53YXNtYCBhcyBhIGJ1bmRsZXIgYXNzZXQgKGBuZXcgVVJMKC4uLiwgaW1wb3J0Lm1ldGEudXJsKWAsIHRoZSBzYW1lXG4gKiBwYXR0ZXJuIHRoZSB0cmFjZS12aWV3ZXIgV29ya2VyIHVzZXMpIGFuZCBjb21waWxlIGl0IG9uY2UsIGxhemlseSDigJQgdGhlXG4gKiBXQVNNIGlzIGZldGNoZWQgb25seSB0aGUgZmlyc3QgdGltZSBhIHpzdGQgcGF5bG9hZCBpcyBhY3R1YWxseSBkZWNvZGVkLlxuICovXG5pbXBvcnQgeyByZWdpc3RlclpzdGREZWNvZGVyIH0gZnJvbSAnQHdvcmtmbG93L2NvcmUvc2VyaWFsaXphdGlvbi1mb3JtYXQnO1xuXG5sZXQgcmVnaXN0ZXJlZCA9IGZhbHNlO1xubGV0IG1vZHVsZVByb21pc2U6IFByb21pc2U8V2ViQXNzZW1ibHkuTW9kdWxlPiB8IHVuZGVmaW5lZDtcblxuZnVuY3Rpb24gbG9hZFdhc21Nb2R1bGUoKTogUHJvbWlzZTxXZWJBc3NlbWJseS5Nb2R1bGU+IHtcbiAgaWYgKCFtb2R1bGVQcm9taXNlKSB7XG4gICAgY29uc3QgdXJsID0gbmV3IFVSTCgnQHRvb3RhbGxuYXRlL3pzdGQtd2FzbS96c3RkLndhc20nLCBpbXBvcnQubWV0YS51cmwpO1xuICAgIG1vZHVsZVByb21pc2UgPSBmZXRjaCh1cmwpXG4gICAgICAudGhlbigocmVzKSA9PiByZXMuYXJyYXlCdWZmZXIoKSlcbiAgICAgIC50aGVuKChieXRlcykgPT4gV2ViQXNzZW1ibHkuY29tcGlsZShieXRlcykpO1xuICB9XG4gIHJldHVybiBtb2R1bGVQcm9taXNlO1xufVxuXG4vKipcbiAqIFJlZ2lzdGVyIHRoZSBicm93c2VyIHpzdGQgZGVjb2RlciB3aXRoIGBAd29ya2Zsb3cvY29yZWAgKGlkZW1wb3RlbnQpLlxuICogQ2FsbCB0aGlzIGJlZm9yZSBoeWRyYXRpbmcgcGF5bG9hZHMgdGhhdCBtYXkgYmUgenN0ZC1jb21wcmVzc2VkOyB0aGVcbiAqIGFjdHVhbCBXQVNNIGNvbXBpbGUgKyBkZWNvZGUgaGFwcGVucyBsYXppbHkgb24gZmlyc3QgdXNlLlxuICovXG5leHBvcnQgZnVuY3Rpb24gZW5zdXJlWnN0ZERlY29kZXJSZWdpc3RlcmVkKCk6IHZvaWQge1xuICBpZiAocmVnaXN0ZXJlZCkgcmV0dXJuO1xuICByZWdpc3RlcmVkID0gdHJ1ZTtcbiAgcmVnaXN0ZXJac3RkRGVjb2Rlcihhc3luYyAocGF5bG9hZCkgPT4ge1xuICAgIGNvbnN0IHsgZGVjb21wcmVzc0J5dGVzIH0gPSBhd2FpdCBpbXBvcnQoJ0B0b290YWxsbmF0ZS96c3RkLXdhc20nKTtcbiAgICBjb25zdCB3YXNtTW9kdWxlID0gYXdhaXQgbG9hZFdhc21Nb2R1bGUoKTtcbiAgICByZXR1cm4gZGVjb21wcmVzc0J5dGVzKHdhc21Nb2R1bGUsIHBheWxvYWQpO1xuICB9KTtcbn1cbiJdfQ==
41
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoienN0ZC1icm93c2VyLWRlY29kZXIuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvbGliL3pzdGQtYnJvd3Nlci1kZWNvZGVyLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOzs7Ozs7Ozs7Ozs7R0FZRztBQUNILE9BQU8sRUFBRSxtQkFBbUIsRUFBRSxNQUFNLHFDQUFxQyxDQUFDO0FBRTFFLElBQUksVUFBVSxHQUFHLEtBQUssQ0FBQztBQUN2QixJQUFJLGFBQXNELENBQUM7QUFFM0QsU0FBUyxjQUFjO0lBQ3JCLElBQUksQ0FBQyxhQUFhLEVBQUUsQ0FBQztRQUNuQixNQUFNLEdBQUcsR0FBRyxJQUFJLEdBQUcsQ0FBQyxrQ0FBa0MsRUFBRSxNQUFNLENBQUMsSUFBSSxDQUFDLEdBQUcsQ0FBQyxDQUFDO1FBQ3pFLGFBQWEsR0FBRyxLQUFLLENBQUMsR0FBRyxDQUFDO2FBQ3ZCLElBQUksQ0FBQyxDQUFDLEdBQUcsRUFBRSxFQUFFLENBQUMsR0FBRyxDQUFDLFdBQVcsRUFBRSxDQUFDO2FBQ2hDLElBQUksQ0FBQyxDQUFDLEtBQUssRUFBRSxFQUFFLENBQUMsV0FBVyxDQUFDLE9BQU8sQ0FBQyxLQUFLLENBQUMsQ0FBQyxDQUFDO0lBQ2pELENBQUM7SUFDRCxPQUFPLGFBQWEsQ0FBQztBQUN2QixDQUFDO0FBRUQ7Ozs7R0FJRztBQUNILE1BQU0sVUFBVSwyQkFBMkI7SUFDekMsSUFBSSxVQUFVO1FBQUUsT0FBTztJQUN2QixVQUFVLEdBQUcsSUFBSSxDQUFDO0lBQ2xCLG1CQUFtQixDQUFDLEtBQUssRUFBRSxPQUFPLEVBQUUsRUFBRTtRQUNwQyxNQUFNLEVBQUUsZUFBZSxFQUFFLEdBQUcsTUFBTSxNQUFNLENBQUMsd0JBQXdCLENBQUMsQ0FBQztRQUNuRSxNQUFNLFVBQVUsR0FBRyxNQUFNLGNBQWMsRUFBRSxDQUFDO1FBQzFDLE9BQU8sZUFBZSxDQUFDLFVBQVUsRUFBRSxPQUFPLENBQUMsQ0FBQztJQUM5QyxDQUFDLENBQUMsQ0FBQztBQUNMLENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyIvKipcbiAqIEJyb3dzZXIgenN0ZCBkZWNvZGVyIGZvciB0aGUgbzExeSByZWFkIHBhdGguXG4gKlxuICogVGhlIFdlYiBgRGVjb21wcmVzc2lvblN0cmVhbWAgaGFzIG5vIHpzdGQgc3VwcG9ydCwgc28gYEB3b3JrZmxvdy9jb3JlYCdzXG4gKiBgaHlkcmF0ZURhdGFXaXRoS2V5YCBkZWxlZ2F0ZXMgenN0ZCBpbmZsYXRpb24gdG8gYSBkZWNvZGVyIHJlZ2lzdGVyZWQgdmlhXG4gKiBgcmVnaXN0ZXJac3RkRGVjb2RlcmAuIFRoaXMgbW9kdWxlIHN1cHBsaWVzIHRoYXQgZGVjb2RlciwgYmFja2VkIGJ5IHRoZVxuICogYEB0b290YWxsbmF0ZS96c3RkLXdhc21gIHNpbmdsZS1maWxlIFdBU00gZGVjb2Rlci5cbiAqXG4gKiBUaGUgcGFja2FnZSBsZWF2ZXMgV0FTTSBzb3VyY2luZyB0byB0aGUgY2FsbGVyOyB3ZSByZXNvbHZlIHRoZSBzaGlwcGVkXG4gKiBgenN0ZC53YXNtYCBhcyBhIGJ1bmRsZXIgYXNzZXQgKGBuZXcgVVJMKC4uLiwgaW1wb3J0Lm1ldGEudXJsKWAsIHRoZSBzYW1lXG4gKiBwYXR0ZXJuIHRoZSB0cmFjZS12aWV3ZXIgV29ya2VyIHVzZXMpIGFuZCBjb21waWxlIGl0IG9uY2UsIGxhemlseTogdGhlXG4gKiBXQVNNIGlzIGZldGNoZWQgb25seSB0aGUgZmlyc3QgdGltZSBhIHpzdGQgcGF5bG9hZCBpcyBhY3R1YWxseSBkZWNvZGVkLlxuICovXG5pbXBvcnQgeyByZWdpc3RlclpzdGREZWNvZGVyIH0gZnJvbSAnQHdvcmtmbG93L2NvcmUvc2VyaWFsaXphdGlvbi1mb3JtYXQnO1xuXG5sZXQgcmVnaXN0ZXJlZCA9IGZhbHNlO1xubGV0IG1vZHVsZVByb21pc2U6IFByb21pc2U8V2ViQXNzZW1ibHkuTW9kdWxlPiB8IHVuZGVmaW5lZDtcblxuZnVuY3Rpb24gbG9hZFdhc21Nb2R1bGUoKTogUHJvbWlzZTxXZWJBc3NlbWJseS5Nb2R1bGU+IHtcbiAgaWYgKCFtb2R1bGVQcm9taXNlKSB7XG4gICAgY29uc3QgdXJsID0gbmV3IFVSTCgnQHRvb3RhbGxuYXRlL3pzdGQtd2FzbS96c3RkLndhc20nLCBpbXBvcnQubWV0YS51cmwpO1xuICAgIG1vZHVsZVByb21pc2UgPSBmZXRjaCh1cmwpXG4gICAgICAudGhlbigocmVzKSA9PiByZXMuYXJyYXlCdWZmZXIoKSlcbiAgICAgIC50aGVuKChieXRlcykgPT4gV2ViQXNzZW1ibHkuY29tcGlsZShieXRlcykpO1xuICB9XG4gIHJldHVybiBtb2R1bGVQcm9taXNlO1xufVxuXG4vKipcbiAqIFJlZ2lzdGVyIHRoZSBicm93c2VyIHpzdGQgZGVjb2RlciB3aXRoIGBAd29ya2Zsb3cvY29yZWAgKGlkZW1wb3RlbnQpLlxuICogQ2FsbCB0aGlzIGJlZm9yZSBoeWRyYXRpbmcgcGF5bG9hZHMgdGhhdCBtYXkgYmUgenN0ZC1jb21wcmVzc2VkOyB0aGVcbiAqIGFjdHVhbCBXQVNNIGNvbXBpbGUgKyBkZWNvZGUgaGFwcGVucyBsYXppbHkgb24gZmlyc3QgdXNlLlxuICovXG5leHBvcnQgZnVuY3Rpb24gZW5zdXJlWnN0ZERlY29kZXJSZWdpc3RlcmVkKCk6IHZvaWQge1xuICBpZiAocmVnaXN0ZXJlZCkgcmV0dXJuO1xuICByZWdpc3RlcmVkID0gdHJ1ZTtcbiAgcmVnaXN0ZXJac3RkRGVjb2Rlcihhc3luYyAocGF5bG9hZCkgPT4ge1xuICAgIGNvbnN0IHsgZGVjb21wcmVzc0J5dGVzIH0gPSBhd2FpdCBpbXBvcnQoJ0B0b290YWxsbmF0ZS96c3RkLXdhc20nKTtcbiAgICBjb25zdCB3YXNtTW9kdWxlID0gYXdhaXQgbG9hZFdhc21Nb2R1bGUoKTtcbiAgICByZXR1cm4gZGVjb21wcmVzc0J5dGVzKHdhc21Nb2R1bGUsIHBheWxvYWQpO1xuICB9KTtcbn1cbiJdfQ==
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@workflow/web-shared",
3
3
  "description": "Shared components for Workflow Observability UI",
4
- "version": "5.0.0-beta.43",
4
+ "version": "5.0.0-beta.46",
5
5
  "private": false,
6
6
  "files": [
7
7
  "dist",
@@ -55,9 +55,9 @@
55
55
  "streamdown": "2.3.0",
56
56
  "tailwind-merge": "3.5.0",
57
57
  "tailwindcss": "4",
58
- "@workflow/core": "5.0.0-beta.43",
59
- "@workflow/utils": "5.0.0-beta.8",
60
- "@workflow/world": "5.0.0-beta.28"
58
+ "@workflow/core": "5.0.0-beta.46",
59
+ "@workflow/utils": "5.0.0-beta.9",
60
+ "@workflow/world": "5.0.0-beta.31"
61
61
  },
62
62
  "devDependencies": {
63
63
  "@biomejs/biome": "^2.4.4",
@@ -67,7 +67,7 @@
67
67
  "ai": "6.0.116",
68
68
  "typescript": "^6.0.3",
69
69
  "vitest": "^4.1.10",
70
- "@workflow/errors": "5.0.0-beta.17",
70
+ "@workflow/errors": "5.0.0-beta.18",
71
71
  "@workflow/tsconfig": "5.0.0-beta.0"
72
72
  },
73
73
  "scripts": {
@@ -10,7 +10,10 @@ import type {
10
10
  } from 'react';
11
11
  import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
12
12
  import { Virtuoso, type VirtuosoHandle } from 'react-virtuoso';
13
- import { findDuplicateEventIds } from '../lib/duplicate-events';
13
+ import {
14
+ DUPLICATE_EVENT_MESSAGE,
15
+ findDuplicateEventIds,
16
+ } from '../lib/duplicate-events';
14
17
  import {
15
18
  type ExactIdSearchResult,
16
19
  type ExactWorkflowSearchIdKind,
@@ -18,13 +21,14 @@ import {
18
21
  parseExactWorkflowSearchId,
19
22
  } from '../lib/exact-event-search-id';
20
23
  import { isEncryptedMarker } from '../lib/hydration';
24
+ import { isSealedNoopEvent, SEALED_EVENT_MESSAGE } from '../lib/sealed-events';
21
25
  import { useToast } from '../lib/toast';
22
26
  import { formatDuration } from '../lib/utils';
23
27
  import { AttrSetEventBlock } from './sidebar/attributes-block';
24
28
  import { ContextCardProvider } from './ui/context-card';
25
29
  import { DataInspector, DecryptClickContext } from './ui/data-inspector';
26
30
  import { DecryptButton } from './ui/decrypt-button';
27
- import { DuplicateEventTooltip } from './ui/duplicate-event-tooltip';
31
+ import { EventNoticeTooltip } from './ui/duplicate-event-tooltip';
28
32
  import {
29
33
  ErrorStackBlock,
30
34
  isStructuredError,
@@ -142,6 +146,11 @@ function getStatusDotColor(eventType: string): string {
142
146
  ) {
143
147
  return 'var(--ds-blue-700)';
144
148
  }
149
+ // Sealed positions → dim gray, one step quieter than pending: the row is
150
+ // log filler the run never observed.
151
+ if (eventType === 'noop') {
152
+ return 'var(--ds-gray-500)';
153
+ }
145
154
  // Created/pending → gray
146
155
  return 'var(--ds-gray-600)';
147
156
  }
@@ -150,7 +159,7 @@ function getStatusDotColor(eventType: string): string {
150
159
  * Build a map from correlationId (stepId) → display name using step_created
151
160
  * events, and parse the workflow name from the run.
152
161
  */
153
- function buildNameMaps(
162
+ export function buildNameMaps(
154
163
  events: Event[] | null,
155
164
  run: WorkflowRun | null
156
165
  ): {
@@ -164,7 +173,9 @@ function buildNameMaps(
164
173
  for (const event of events) {
165
174
  if (event.eventType === 'step_created' && event.correlationId) {
166
175
  const stepName = event.eventData?.stepName ?? '';
167
- const parsed = parseStepName(String(stepName));
176
+ const parsed =
177
+ parseStepName(String(stepName)) ??
178
+ parseWorkflowName(String(stepName));
168
179
  correlationNameMap.set(
169
180
  event.correlationId,
170
181
  parsed?.shortName ?? stepName
@@ -235,7 +246,7 @@ export function buildDurationMap(
235
246
  type === 'workflow_started'
236
247
  ) {
237
248
  startedTimes.set(key, ts);
238
- // The queued duration is anchored on the first start event only
249
+ // The queued duration is anchored on the first start event only, since
239
250
  // subsequent step_started events come from retries.
240
251
  if (!firstStartedTimes.has(key)) {
241
252
  firstStartedTimes.set(key, ts);
@@ -295,14 +306,15 @@ function isRunLevel(eventType: string): boolean {
295
306
  eventType === 'workflow_started' ||
296
307
  eventType === 'workflow_completed' ||
297
308
  eventType === 'workflow_failed' ||
298
- // attr_set carries a dedup correlationId rather than a child entity ID,
299
- // so it groups and labels with the run itself.
300
- eventType === 'attr_set'
309
+ // attr_set and noop carry a dedup/positional correlationId rather than a
310
+ // child entity ID, so they group and label with the run itself.
311
+ eventType === 'attr_set' ||
312
+ eventType === 'noop'
301
313
  );
302
314
  }
303
315
 
304
316
  // ──────────────────────────────────────────────────────────────────────────
305
- // Tree gutter fixed-width, shows branch lines only for the selected group
317
+ // Tree gutter: fixed-width, shows branch lines only for the selected group
306
318
  // ──────────────────────────────────────────────────────────────────────────
307
319
 
308
320
  /** Fixed gutter width: 20px root area + 16px for one branch lane */
@@ -453,7 +465,7 @@ function TreeGutter({
453
465
  }
454
466
 
455
467
  // ──────────────────────────────────────────────────────────────────────────
456
- // Copyable cell shows a copy button on hover
468
+ // Copyable cell: shows a copy button on hover
457
469
  // ──────────────────────────────────────────────────────────────────────────
458
470
 
459
471
  function CopyableCell({
@@ -545,7 +557,7 @@ function deepParseJson(value: unknown): unknown {
545
557
  }
546
558
  if (value !== null && typeof value === 'object') {
547
559
  // Preserve objects with custom constructors (e.g., encrypted markers,
548
- // class instance refs) don't destructure them into plain objects
560
+ // class instance refs); don't destructure them into plain objects
549
561
  if (value.constructor !== Object) {
550
562
  return value;
551
563
  }
@@ -633,13 +645,13 @@ function PayloadBlock({
633
645
  );
634
646
  }
635
647
 
636
- // Attribute changes render the changed keys and the writer instead of
648
+ // Attribute changes: render the changed keys and the writer instead of
637
649
  // the raw JSON payload.
638
650
  if (eventType === 'attr_set') {
639
651
  return <AttrSetEventBlock data={cleaned} />;
640
652
  }
641
653
 
642
- // Cancellation reason render the free-text reason as a readable line
654
+ // Cancellation reason: render the free-text reason as a readable line
643
655
  // instead of a raw JSON payload (the only field run_cancelled carries).
644
656
  if (eventType === 'run_cancelled') {
645
657
  const cancelReason =
@@ -809,6 +821,10 @@ interface EventsListProps {
809
821
  onDecrypt?: () => void;
810
822
  /** Whether the encryption key is currently being fetched. */
811
823
  isDecrypting?: boolean;
824
+ /** Whether decryption is unavailable. */
825
+ isDecryptDisabled?: boolean;
826
+ /** Explains why decryption is unavailable. */
827
+ decryptDisabledReason?: string;
812
828
  /** Run-level hint: the run contains encrypted data (from probe). */
813
829
  hasEncryptedData?: boolean;
814
830
  /** Fetch events for an exact correlation or event ID. */
@@ -896,6 +912,12 @@ export function EventRow({
896
912
  ? '__run__'
897
913
  : (event.correlationId ?? undefined);
898
914
 
915
+ const isSealed = isSealedNoopEvent(event);
916
+ const rowNotice = isDuplicate
917
+ ? DUPLICATE_EVENT_MESSAGE
918
+ : isSealed
919
+ ? SEALED_EVENT_MESSAGE
920
+ : undefined;
899
921
  const statusDotColor = getStatusDotColor(event.eventType);
900
922
  const createdAt = new Date(event.createdAt);
901
923
  const occurredAt = parseEventDate(event.occurredAt);
@@ -1061,7 +1083,7 @@ export function EventRow({
1061
1083
  isLaneEnd={isLaneEnd}
1062
1084
  />
1063
1085
 
1064
- {/* Content area dims when unrelated */}
1086
+ {/* Content area: dims when unrelated */}
1065
1087
  <div
1066
1088
  className="flex items-center flex-1 min-w-0"
1067
1089
  style={{ opacity: contentOpacity, transition: 'opacity 150ms' }}
@@ -1109,11 +1131,11 @@ export function EventRow({
1109
1131
 
1110
1132
  {/* Event Type */}
1111
1133
  <div className="font-medium min-w-0 px-4" style={{ flex: '2 1 0%' }}>
1112
- <DuplicateEventTooltip isDuplicate={isDuplicate}>
1134
+ <EventNoticeTooltip notice={rowNotice}>
1113
1135
  <span
1114
1136
  className="inline-flex items-center gap-1.5"
1115
1137
  style={{
1116
- color: isDuplicate
1138
+ color: rowNotice
1117
1139
  ? 'var(--ds-gray-700)'
1118
1140
  : 'var(--ds-gray-900)',
1119
1141
  }}
@@ -1151,7 +1173,7 @@ export function EventRow({
1151
1173
  </span>
1152
1174
  {formatEventType(event.eventType)}
1153
1175
  </span>
1154
- </DuplicateEventTooltip>
1176
+ </EventNoticeTooltip>
1155
1177
  </div>
1156
1178
 
1157
1179
  {/* Name */}
@@ -1179,10 +1201,10 @@ export function EventRow({
1179
1201
  </div>
1180
1202
  </div>
1181
1203
 
1182
- {/* Expanded details tree lines continue through this area */}
1204
+ {/* Expanded details: tree lines continue through this area */}
1183
1205
  {isExpanded && (
1184
1206
  <div className="flex">
1185
- {/* Continuation gutter lane line continues if not at lane end */}
1207
+ {/* Continuation gutter: lane line continues if not at lane end */}
1186
1208
  <TreeGutter
1187
1209
  isFirst={false}
1188
1210
  isLast={isLast}
@@ -1286,6 +1308,8 @@ function EventListViewInner({
1286
1308
  onSortOrderChange,
1287
1309
  onDecrypt,
1288
1310
  isDecrypting = false,
1311
+ isDecryptDisabled = false,
1312
+ decryptDisabledReason,
1289
1313
  hasEncryptedData: hasEncryptedDataProp = false,
1290
1314
  onExactIdSearch,
1291
1315
  showSeparateEventOccurrenceTimestamps = false,
@@ -1414,7 +1438,7 @@ function EventListViewInner({
1414
1438
  });
1415
1439
  }, []);
1416
1440
 
1417
- // Event data cache ref avoids re-renders when cache updates
1441
+ // Event data cache: ref avoids re-renders when cache updates
1418
1442
  const eventDataCacheRef = useRef<Map<string, unknown>>(new Map());
1419
1443
  const cacheEventData = useCallback((eventId: string, data: unknown) => {
1420
1444
  eventDataCacheRef.current.set(eventId, data);
@@ -1645,7 +1669,16 @@ function EventListViewInner({
1645
1669
 
1646
1670
  return (
1647
1671
  <DecryptClickContext.Provider
1648
- value={onDecrypt ? { onDecrypt, isDecrypting } : undefined}
1672
+ value={
1673
+ onDecrypt
1674
+ ? {
1675
+ onDecrypt,
1676
+ isDecrypting,
1677
+ isDecryptDisabled,
1678
+ decryptDisabledReason,
1679
+ }
1680
+ : undefined
1681
+ }
1649
1682
  >
1650
1683
  <div className="h-full flex flex-col overflow-hidden">
1651
1684
  <style>{`@keyframes workflow-dot-pulse{0%{transform:scale(1);opacity:.7}70%,100%{transform:scale(2.2);opacity:0}}`}</style>
@@ -1741,6 +1774,8 @@ function EventListViewInner({
1741
1774
  <DecryptButton
1742
1775
  decrypted={!!encryptionKey}
1743
1776
  loading={isDecrypting}
1777
+ disabled={isDecryptDisabled}
1778
+ disabledReason={decryptDisabledReason}
1744
1779
  onClick={onDecrypt}
1745
1780
  />
1746
1781
  )}
@@ -1852,7 +1887,7 @@ function EventListViewInner({
1852
1887
  />
1853
1888
  )}
1854
1889
 
1855
- {/* Fixed footer count + load more */}
1890
+ {/* Fixed footer: count + load more */}
1856
1891
  <div
1857
1892
  className="relative flex-shrink-0 flex items-center h-10 border-t px-4 text-label-12"
1858
1893
  style={{
@@ -199,7 +199,7 @@ function EncryptedFieldBlock() {
199
199
  }
200
200
 
201
201
  /**
202
- * Inline display for an expired field flat label indicating data is no longer available.
202
+ * Inline display for an expired field: flat label indicating data is no longer available.
203
203
  */
204
204
  function ExpiredFieldBlock() {
205
205
  return (
@@ -299,7 +299,7 @@ const attributeOrder: AttributeKey[] = [
299
299
 
300
300
  /**
301
301
  * Rank of an attribute in {@link attributeOrder}. Keys absent from that list
302
- * sort after every listed one rather than before them `indexOf` reports a
302
+ * sort after every listed one rather than before them: `indexOf` reports a
303
303
  * miss as `-1`, which is truthy, so a `|| 0` fallback never fires.
304
304
  */
305
305
  const attributeOrderIndex = (attribute: string): number => {
@@ -449,7 +449,7 @@ const attributeToDisplayFn: Record<
449
449
  receivedCount: (value: unknown) => String(value),
450
450
  lastReceivedAt: localMillisecondTimeOrNull,
451
451
  disposedAt: localMillisecondTimeOrNull,
452
- // Internal resume plumbing not surfaced in the UI
452
+ // Internal resume plumbing, not surfaced in the UI
453
453
  resumeContext: (_value: unknown) => null,
454
454
  resumeId: (_value: unknown) => null,
455
455
  resumeCapabilities: (_value: unknown) => null,
@@ -467,14 +467,14 @@ const attributeToDisplayFn: Record<
467
467
  projectId: (_value: unknown) => null,
468
468
  environment: (_value: unknown) => null,
469
469
  executionContext: (_value: unknown) => null,
470
- // Attributes string-string metadata attached to the run.
470
+ // Attributes: string-string metadata attached to the run.
471
471
  // Rendered as key-value rows in its own collapsible section;
472
472
  // if empty/missing, hidden by the hasDisplayContent gate.
473
473
  attributes: (value: unknown) => {
474
474
  if (!hasDisplayContent(value)) return null;
475
475
  return <RunAttributesCard attributes={value as Record<string, string>} />;
476
476
  },
477
- // Dates wrapped with TimestampTooltip showing UTC/local + relative time
477
+ // Dates: wrapped with TimestampTooltip showing UTC/local + relative time
478
478
  occurredAt: timestampWithTooltipOrNull,
479
479
  createdAt: timestampWithTooltipOrNull,
480
480
  startedAt: timestampWithTooltipOrNull,
@@ -678,7 +678,7 @@ const attributeToDisplayFn: Record<
678
678
  },
679
679
  // Internal encryption plumbing: the run's X25519 public key, used by
680
680
  // cross-run writers to seal payloads to this run. Not actionable for users
681
- // and not secret hidden rather than rendered as 44 opaque base64 chars.
681
+ // and not secret, so hidden rather than rendered as 44 opaque base64 chars.
682
682
  encryptionPublicKey: (_value: unknown) => null,
683
683
  };
684
684
 
@@ -836,6 +836,8 @@ export const AttributePanel = ({
836
836
  onRunClick,
837
837
  onDecrypt,
838
838
  isDecrypting = false,
839
+ isDecryptDisabled = false,
840
+ decryptDisabledReason,
839
841
  resource,
840
842
  }: {
841
843
  data: Record<string, unknown>;
@@ -852,7 +854,11 @@ export const AttributePanel = ({
852
854
  onDecrypt?: () => void;
853
855
  /** Whether decryption is currently in progress */
854
856
  isDecrypting?: boolean;
855
- /** Resource type of the selected span — used to show targeted loading skeletons. */
857
+ /** Whether decryption is unavailable */
858
+ isDecryptDisabled?: boolean;
859
+ /** Explains why decryption is unavailable */
860
+ decryptDisabledReason?: string;
861
+ /** Resource type of the selected span, used to show targeted loading skeletons. */
856
862
  resource?: string;
857
863
  }) => {
858
864
  // Extract workflowCoreVersion from executionContext for display
@@ -943,6 +949,8 @@ export const AttributePanel = ({
943
949
  ? {
944
950
  onDecrypt,
945
951
  isDecrypting,
952
+ isDecryptDisabled,
953
+ decryptDisabledReason,
946
954
  hasEncryptedData: outerDecryptCtx?.hasEncryptedData,
947
955
  }
948
956
  : outerDecryptCtx;
@@ -25,7 +25,14 @@ export function EncryptedDataBlock() {
25
25
  </pre>
26
26
  <div className="absolute inset-0 flex items-center justify-center">
27
27
  {ctx ? (
28
- <Button onClick={ctx.onDecrypt} disabled={ctx.isDecrypting} size="xs">
28
+ <Button
29
+ onClick={ctx.onDecrypt}
30
+ disabled={ctx.isDecrypting || ctx.isDecryptDisabled}
31
+ title={
32
+ ctx.isDecryptDisabled ? ctx.decryptDisabledReason : undefined
33
+ }
34
+ size="xs"
35
+ >
29
36
  {ctx.isDecrypting ? (
30
37
  <Spinner size={10} />
31
38
  ) : (
@@ -57,7 +57,7 @@ export interface SelectedSpanInfo {
57
57
  /**
58
58
  * Panel component for workflow traces that displays entity details.
59
59
  *
60
- * This component is rendered OUTSIDE the trace viewer context it
60
+ * This component is rendered OUTSIDE the trace viewer context: it
61
61
  * receives all data via props rather than reading from context.
62
62
  */
63
63
  export function EntityDetailPanel({
@@ -71,6 +71,8 @@ export function EntityDetailPanel({
71
71
  encryptionKey,
72
72
  onDecrypt,
73
73
  isDecrypting = false,
74
+ isDecryptDisabled = false,
75
+ decryptDisabledReason,
74
76
  selectedSpan,
75
77
  showSeparateEventOccurrenceTimestamps = false,
76
78
  getModuleSourceUrl,
@@ -100,6 +102,10 @@ export function EntityDetailPanel({
100
102
  onDecrypt?: () => void;
101
103
  /** Whether the encryption key is currently being fetched */
102
104
  isDecrypting?: boolean;
105
+ /** Whether decryption is unavailable */
106
+ isDecryptDisabled?: boolean;
107
+ /** Explains why decryption is unavailable */
108
+ decryptDisabledReason?: string;
103
109
  /** Info about the currently selected span from the trace viewer */
104
110
  selectedSpan: SelectedSpanInfo | null;
105
111
  /** Show occurredAt separately instead of folding it into the Created timestamp. */
@@ -315,7 +321,15 @@ export function EntityDetailPanel({
315
321
  <div className="flex h-full flex-col">
316
322
  <DecryptClickContext.Provider
317
323
  value={
318
- onDecrypt ? { onDecrypt, isDecrypting, hasEncryptedData } : undefined
324
+ onDecrypt
325
+ ? {
326
+ onDecrypt,
327
+ isDecrypting,
328
+ isDecryptDisabled,
329
+ decryptDisabledReason,
330
+ hasEncryptedData,
331
+ }
332
+ : undefined
319
333
  }
320
334
  >
321
335
  <div className="flex-1 overflow-y-auto px-4 pb-8">
@@ -394,6 +408,8 @@ export function EntityDetailPanel({
394
408
  onRunClick={onRunClick}
395
409
  onDecrypt={onDecrypt}
396
410
  isDecrypting={isDecrypting}
411
+ isDecryptDisabled={isDecryptDisabled}
412
+ decryptDisabledReason={decryptDisabledReason}
397
413
  resource={resource}
398
414
  />
399
415
 
@@ -2,7 +2,12 @@
2
2
 
3
3
  import { type Event, getEventDataRefFields } from '@workflow/world';
4
4
  import { useCallback, useLayoutEffect, useMemo, useRef, useState } from 'react';
5
+ import { DUPLICATE_EVENT_MESSAGE } from '../../lib/duplicate-events';
5
6
  import { hasEncryptedFields, isExpiredMarker } from '../../lib/hydration';
7
+ import {
8
+ isSealedNoopEvent,
9
+ SEALED_EVENT_MESSAGE,
10
+ } from '../../lib/sealed-events';
6
11
  import {
7
12
  Collapsible,
8
13
  CollapsibleContent,
@@ -10,7 +15,7 @@ import {
10
15
  CollapsibleTrigger,
11
16
  } from '../ui/collapsible';
12
17
  import { RunClickContext, StreamClickContext } from '../ui/data-inspector';
13
- import { DuplicateEventTooltip } from '../ui/duplicate-event-tooltip';
18
+ import { EventNoticeTooltip } from '../ui/duplicate-event-tooltip';
14
19
  import { ErrorCard } from '../ui/error-card';
15
20
  import { ErrorStackBlock, isStructuredError } from '../ui/error-stack-block';
16
21
  import { Skeleton } from '../ui/skeleton';
@@ -110,6 +115,11 @@ function EventItem({
110
115
  void loadEventData(true);
111
116
  }, [encryptionKey, loadEventData]);
112
117
 
118
+ const rowNotice = isDuplicate
119
+ ? DUPLICATE_EVENT_MESSAGE
120
+ : isSealedNoopEvent(event)
121
+ ? SEALED_EVENT_MESSAGE
122
+ : undefined;
113
123
  const createdAt = new Date(event.createdAt);
114
124
  const occurredAt = parseDateValue(event.occurredAt);
115
125
  const displayedCreatedAt = showSeparateEventOccurrenceTimestamps
@@ -139,15 +149,15 @@ function EventItem({
139
149
  >
140
150
  <CollapsibleTrigger className="px-3 py-2">
141
151
  <div className="flex w-full items-center justify-between gap-3">
142
- <DuplicateEventTooltip isDuplicate={isDuplicate}>
152
+ <EventNoticeTooltip notice={rowNotice}>
143
153
  <span
144
154
  className={`text-label-12 font-mono ${
145
- isDuplicate ? 'text-gray-700' : 'text-gray-1000'
155
+ rowNotice ? 'text-gray-700' : 'text-gray-1000'
146
156
  }`}
147
157
  >
148
158
  {event.eventType}
149
159
  </span>
150
- </DuplicateEventTooltip>
160
+ </EventNoticeTooltip>
151
161
  <span className="shrink-0 text-label-13 text-gray-900">
152
162
  {displayedCreatedAtTime}
153
163
  </span>
@@ -242,7 +252,7 @@ function EventDataBlock({
242
252
  eventType: string;
243
253
  data: unknown;
244
254
  }) {
245
- // Expired data show a simple message instead of the raw stub.
255
+ // Expired data: show a message instead of the raw stub.
246
256
  // Check both the top-level eventData and nested sub-fields (result, input, etc.)
247
257
  // since the server stubs each ref field independently.
248
258
  if (isExpiredMarker(data) || hasOnlyExpiredFields(data, eventType)) {
@@ -264,7 +274,7 @@ function EventDataBlock({
264
274
  return <EncryptedDataBlock />;
265
275
  }
266
276
 
267
- // Attribute changes render the changed keys and the writer instead of
277
+ // Attribute changes: render the changed keys and the writer instead of
268
278
  // the raw JSON payload.
269
279
  if (eventType === 'attr_set') {
270
280
  return <AttrSetEventBlock data={data} />;
@@ -116,13 +116,13 @@ export function ResolveHookModal({
116
116
  aria-modal="true"
117
117
  aria-labelledby="resolve-hook-modal-title"
118
118
  >
119
- {/* Backdrop matches Geist dialog ::backdrop */}
119
+ {/* Backdrop: matches Geist dialog ::backdrop */}
120
120
  <div
121
121
  className="absolute inset-0 bg-black/70"
122
122
  onClick={isSubmitting ? undefined : onClose}
123
123
  />
124
124
 
125
- {/* Modal card matches Geist dialog.geist-dialog */}
125
+ {/* Modal card: matches Geist dialog.geist-dialog */}
126
126
  <div className="relative z-10 w-[480px] max-w-[calc(100%_-_32px)] overflow-hidden !border-none bg-background-100 text-gray-1000 material-menu">
127
127
  {/* Header */}
128
128
  <div className="flex items-center justify-between px-6 py-4">
@@ -29,6 +29,8 @@ export interface SidebarDataContextValue {
29
29
  encryptionKey?: Uint8Array;
30
30
  onDecrypt?: () => void;
31
31
  isDecrypting?: boolean;
32
+ isDecryptDisabled?: boolean;
33
+ decryptDisabledReason?: string;
32
34
  hasEncryptedData?: boolean;
33
35
  /** Show occurredAt separately instead of folding it into the Created timestamp. */
34
36
  showSeparateEventOccurrenceTimestamps?: boolean;
@@ -30,7 +30,7 @@ interface StreamViewerProps {
30
30
  }
31
31
 
32
32
  // ──────────────────────────────────────────────────────────────────────────
33
- // Chunk row memoized to prevent remounts during polling
33
+ // Chunk row: memoized to prevent remounts during polling
34
34
  // ──────────────────────────────────────────────────────────────────────────
35
35
 
36
36
  const ChunkRow = React.memo(function ChunkRow({ chunk }: { chunk: Chunk }) {
@@ -2,11 +2,11 @@
2
2
  * Width model for the span detail panel: the stored value is the user's
3
3
  * preferred width (absolute px, persisted to localStorage on explicit
4
4
  * interaction only), while the container-relative maximum is applied at
5
- * render time so a width preferred on a wide screen survives a narrower
5
+ * render time, so a width preferred on a wide screen survives a narrower
6
6
  * session and restores when the viewer grows again.
7
7
  */
8
8
 
9
- /** Floor for the detail panel matches the previous `clamp(280px, …)` floor. */
9
+ /** Floor for the detail panel; matches the previous `clamp(280px, …)` floor. */
10
10
  export const PANEL_MIN_WIDTH = 280;
11
11
 
12
12
  /**
@@ -15,13 +15,13 @@ export const PANEL_MIN_WIDTH = 280;
15
15
  */
16
16
  export const PANEL_HARD_MAX_WIDTH = 1300;
17
17
 
18
- /** Default width matches the previous fixed 360px column. */
18
+ /** Default width; matches the previous fixed 360px column. */
19
19
  export const PANEL_DEFAULT_WIDTH = 360;
20
20
 
21
21
  /**
22
22
  * Width the main (event list + timeline) area keeps while the panel is
23
23
  * resized or the container shrinks. Below `PANEL_MIN_WIDTH + MAIN_MIN_WIDTH`
24
- * of container the panel gives way first, compressing below its own floor
24
+ * of container the panel gives way first, compressing below its own floor:
25
25
  * the list and timeline stay usable at the panel's expense.
26
26
  */
27
27
  export const MAIN_MIN_WIDTH = 400;
@@ -64,7 +64,7 @@ function useSelectedSpanInfo(): SelectedSpanInfo | null {
64
64
  * The span detail aside: content, prev/next/close header, J/K navigation, and
65
65
  * the resizable left border with its width model (see detail-panel-width.ts).
66
66
  *
67
- * Always mounted it renders null without a selection so the panel width
67
+ * Always mounted (it renders null without a selection) so the panel width
68
68
  * survives closing and reopening within a session.
69
69
  */
70
70
  export function TraceDetailPanel({
@@ -261,6 +261,8 @@ export function TraceDetailPanel({
261
261
  encryptionKey={sidebar.encryptionKey}
262
262
  onDecrypt={sidebar.onDecrypt}
263
263
  isDecrypting={sidebar.isDecrypting}
264
+ isDecryptDisabled={sidebar.isDecryptDisabled}
265
+ decryptDisabledReason={sidebar.decryptDisabledReason}
264
266
  selectedSpan={selectedSpan}
265
267
  showSeparateEventOccurrenceTimestamps={
266
268
  sidebar.showSeparateEventOccurrenceTimestamps
@@ -35,7 +35,7 @@ interface DraggableBorderProps {
35
35
  * double-click reset, pointer-capture dragging (works with touch/pen, no
36
36
  * ghost image), and keyboard/ARIA window-splitter support.
37
37
  *
38
- * The panel's positioning ancestor must not clip overflow the strip hangs
38
+ * The panel's positioning ancestor must not clip overflow: the strip hangs
39
39
  * ~8px past the panel edge.
40
40
  */
41
41
  export function DraggableBorder({
@@ -200,9 +200,9 @@ export const Minimap = memo(function Minimap({
200
200
 
201
201
  // Density canvas: one thin line per span across the full run. Sizing is
202
202
  // read live and redraws run straight from the ResizeObserver (which fires
203
- // before paint), so layout changes e.g. the detail panel opening never
203
+ // before paint), so layout changes (e.g. the detail panel opening) never
204
204
  // paint a frame with a stale, stretched bitmap.
205
- // biome-ignore lint/correctness/useExhaustiveDependencies: themeVersion is a redraw trigger the canvas re-resolves its token colors when the theme flips
205
+ // biome-ignore lint/correctness/useExhaustiveDependencies: themeVersion is a redraw trigger; the canvas re-resolves its token colors when the theme flips
206
206
  useLayoutEffect(() => {
207
207
  const canvas = canvasRef.current;
208
208
  const container = containerRef.current;
@@ -276,7 +276,7 @@ export const Minimap = memo(function Minimap({
276
276
  return { mode: 'resize-right' };
277
277
  }
278
278
  // Panning a full-extent window is a no-op, so let a drag on the brush
279
- // start a selection instead the natural first gesture on the map.
279
+ // start a selection instead, the natural first gesture on the map.
280
280
  const { start, end } = viewportRef.current;
281
281
  const isFullExtent =
282
282
  start - rootStartMs < rootDurationMs * 0.001 &&