@workflow/web-shared 5.0.0-beta.13 → 5.0.0-beta.15

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 (38) hide show
  1. package/dist/components/new-trace-viewer/components/event-list.js +3 -3
  2. package/dist/components/new-trace-viewer/components/timeline.d.ts.map +1 -1
  3. package/dist/components/new-trace-viewer/components/timeline.js +26 -14
  4. package/dist/components/new-trace-viewer/components/timeline.module.css +31 -0
  5. package/dist/components/new-trace-viewer/utils.d.ts +1 -1
  6. package/dist/components/new-trace-viewer/utils.d.ts.map +1 -1
  7. package/dist/components/new-trace-viewer/utils.js +12 -5
  8. package/dist/components/sidebar/attribute-panel.d.ts.map +1 -1
  9. package/dist/components/sidebar/attribute-panel.js +8 -4
  10. package/dist/components/sidebar/entity-detail-panel.d.ts.map +1 -1
  11. package/dist/components/sidebar/entity-detail-panel.js +12 -11
  12. package/dist/components/sidebar/events-list.d.ts +3 -1
  13. package/dist/components/sidebar/events-list.d.ts.map +1 -1
  14. package/dist/components/sidebar/events-list.js +4 -3
  15. package/dist/components/sidebar/span-detail-merge.d.ts +16 -0
  16. package/dist/components/sidebar/span-detail-merge.d.ts.map +1 -0
  17. package/dist/components/sidebar/span-detail-merge.js +53 -0
  18. package/dist/components/trace-viewer/util/timing.d.ts +2 -2
  19. package/dist/components/trace-viewer/util/timing.d.ts.map +1 -1
  20. package/dist/components/trace-viewer/util/timing.js +3 -3
  21. package/dist/index.d.ts +1 -1
  22. package/dist/index.d.ts.map +1 -1
  23. package/dist/index.js +2 -2
  24. package/dist/lib/utils.d.ts +11 -0
  25. package/dist/lib/utils.d.ts.map +1 -1
  26. package/dist/lib/utils.js +49 -1
  27. package/package.json +4 -4
  28. package/src/components/new-trace-viewer/components/event-list.tsx +2 -2
  29. package/src/components/new-trace-viewer/components/timeline.module.css +31 -0
  30. package/src/components/new-trace-viewer/components/timeline.tsx +60 -12
  31. package/src/components/new-trace-viewer/utils.ts +12 -4
  32. package/src/components/sidebar/attribute-panel.tsx +7 -3
  33. package/src/components/sidebar/entity-detail-panel.tsx +28 -13
  34. package/src/components/sidebar/events-list.tsx +34 -25
  35. package/src/components/sidebar/span-detail-merge.ts +65 -0
  36. package/src/components/trace-viewer/util/timing.ts +2 -2
  37. package/src/index.ts +1 -0
  38. package/src/lib/utils.ts +57 -0
package/dist/lib/utils.js CHANGED
@@ -66,6 +66,54 @@ export function formatDuration(ms, compact = false) {
66
66
  parts.push(`${seconds}s`);
67
67
  return parts.join(' ');
68
68
  }
69
+ // Locale-aware formatter that always renders exactly two fraction digits.
70
+ // Used for the seconds component of precise durations so values are never
71
+ // snapped to a whole second (and thousands separators stay correct).
72
+ const preciseSecondsFormatter = new Intl.NumberFormat(undefined, {
73
+ minimumFractionDigits: 2,
74
+ maximumFractionDigits: 2,
75
+ });
76
+ /**
77
+ * Formats a duration in milliseconds without snapping to whole seconds.
78
+ *
79
+ * Unlike {@link formatDuration}, this never rounds a sub-minute value up to
80
+ * the next whole second (e.g. 1626ms renders as "1.63s", not "2s").
81
+ *
82
+ * - < 1s: shows whole milliseconds (e.g. "626ms")
83
+ * - 1s – 1m: shows seconds with two decimals (e.g. "1.63s", "45.20s")
84
+ * - >= 1m: decomposes into d/h/m with two-decimal seconds (e.g. "1m 13.45s")
85
+ */
86
+ export function formatDurationPrecise(ms) {
87
+ if (ms === 0) {
88
+ return '0s';
89
+ }
90
+ if (ms < MS_IN_SECOND) {
91
+ const roundedMs = Math.round(ms);
92
+ if (roundedMs < MS_IN_SECOND) {
93
+ return `${roundedMs}ms`;
94
+ }
95
+ }
96
+ const normalizedMs = Math.round(ms / 10) * 10;
97
+ if (normalizedMs < MS_IN_MINUTE) {
98
+ return `${preciseSecondsFormatter.format(normalizedMs / MS_IN_SECOND)}s`;
99
+ }
100
+ const days = Math.floor(normalizedMs / MS_IN_DAY);
101
+ const hours = Math.floor((normalizedMs % MS_IN_DAY) / MS_IN_HOUR);
102
+ const minutes = Math.floor((normalizedMs % MS_IN_HOUR) / MS_IN_MINUTE);
103
+ const seconds = (normalizedMs % MS_IN_MINUTE) / MS_IN_SECOND;
104
+ const parts = [];
105
+ if (days > 0) {
106
+ parts.push(`${days}d`);
107
+ }
108
+ if (hours > 0) {
109
+ parts.push(`${hours}h`);
110
+ }
111
+ if (minutes > 0) {
112
+ parts.push(`${minutes}m`);
113
+ }
114
+ parts.push(`${preciseSecondsFormatter.format(seconds)}s`);
115
+ return parts.join(' ');
116
+ }
69
117
  /**
70
118
  * Returns a formatted pagination display string
71
119
  * @param currentPage - The current page number
@@ -124,4 +172,4 @@ export function identifyStreamSteps(steps) {
124
172
  };
125
173
  });
126
174
  }
127
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidXRpbHMuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvbGliL3V0aWxzLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUVBLE9BQU8sRUFBbUIsSUFBSSxFQUFFLE1BQU0sTUFBTSxDQUFDO0FBQzdDLE9BQU8sRUFBRSxPQUFPLEVBQUUsTUFBTSxnQkFBZ0IsQ0FBQztBQUV6QyxNQUFNLFVBQVUsRUFBRSxDQUFDLEdBQUcsTUFBb0I7SUFDeEMsT0FBTyxPQUFPLENBQUMsSUFBSSxDQUFDLE1BQU0sQ0FBQyxDQUFDLENBQUM7QUFDL0IsQ0FBQztBQUVELE1BQU0sWUFBWSxHQUFHLElBQUksQ0FBQztBQUMxQixNQUFNLFlBQVksR0FBRyxFQUFFLEdBQUcsWUFBWSxDQUFDO0FBQ3ZDLE1BQU0sVUFBVSxHQUFHLEVBQUUsR0FBRyxZQUFZLENBQUM7QUFDckMsTUFBTSxTQUFTLEdBQUcsRUFBRSxHQUFHLFVBQVUsQ0FBQztBQUVsQzs7Ozs7Ozs7Ozs7Ozs7Ozs7R0FpQkc7QUFDSCxNQUFNLFVBQVUsY0FBYyxDQUFDLEVBQVUsRUFBRSxPQUFPLEdBQUcsS0FBSztJQUN4RCxJQUFJLEVBQUUsS0FBSyxDQUFDLEVBQUUsQ0FBQztRQUNiLE9BQU8sSUFBSSxDQUFDO0lBQ2QsQ0FBQztJQUVELElBQUksRUFBRSxHQUFHLFlBQVksRUFBRSxDQUFDO1FBQ3RCLE1BQU0sU0FBUyxHQUFHLElBQUksQ0FBQyxLQUFLLENBQUMsRUFBRSxDQUFDLENBQUM7UUFDakMsT0FBTyxTQUFTLEdBQUcsWUFBWSxDQUFDLENBQUMsQ0FBQyxHQUFHLFNBQVMsSUFBSSxDQUFDLENBQUMsQ0FBQyxJQUFJLENBQUM7SUFDNUQsQ0FBQztJQUVELE1BQU0sU0FBUyxHQUFHLElBQUksQ0FBQyxLQUFLLENBQUMsRUFBRSxHQUFHLFlBQVksQ0FBQyxHQUFHLFlBQVksQ0FBQztJQUUvRCxJQUFJLFNBQVMsR0FBRyxZQUFZLEVBQUUsQ0FBQztRQUM3QixPQUFPLEdBQUcsSUFB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O0lBQ0osQ0FBQyxDQUFDLENBQUM7QUFDUCxDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHR5cGUgeyBTdGVwIH0gZnJvbSAnQHdvcmtmbG93L3dvcmxkJztcbmltcG9ydCB0eXBlIHsgTW9kZWxNZXNzYWdlIH0gZnJvbSAnYWknO1xuaW1wb3J0IHsgdHlwZSBDbGFzc1ZhbHVlLCBjbHN4IH0gZnJvbSAnY2xzeCc7XG5pbXBvcnQgeyB0d01lcmdlIH0gZnJvbSAndGFpbHdpbmQtbWVyZ2UnO1xuXG5leHBvcnQgZnVuY3Rpb24gY24oLi4uaW5wdXRzOiBDbGFzc1ZhbHVlW10pIHtcbiAgcmV0dXJuIHR3TWVyZ2UoY2xzeChpbnB1dHMpKTtcbn1cblxuY29uc3QgTVNfSU5fU0VDT05EID0gMTAwMDtcbmNvbnN0IE1TX0lOX01JTlVURSA9IDYwICogTVNfSU5fU0VDT05EO1xuY29uc3QgTVNfSU5fSE9VUiA9IDYwICogTVNfSU5fTUlOVVRFO1xuY29uc3QgTVNfSU5fREFZID0gMjQgKiBNU19JTl9IT1VSO1xuXG4vKipcbiAqIEZvcm1hdHMgYSBkdXJhdGlvbiBpbiBtaWxsaXNlY29uZHMgdG8gYSBodW1hbi1yZWFkYWJsZSBzdHJpbmcuXG4gKlxuICogQHBhcmFtIG1zIC0gRHVyYXRpb24gaW4gbWlsbGlzZWNvbmRzXG4gKiBAcGFyYW0gY29tcGFjdCAtIElmIHRydWUsIHJldHVybnMgYSBjb21wYWN0IGZvcm1hdCAoZS5nLiwgXCIzODBtc1wiLCBcIjJtIDMwc1wiKS5cbiAqICAgICAgICAgICAgICAgICAgSWYgZmFsc2UgKGRlZmF1bHQpLCByZXR1cm5zIG11bHRpLXBhcnQgZm9ybWF0IChlLmcuLCBcIjFtIDEzc1wiLCBcIjJkIDVoIDNtIDEyc1wiKS5cbiAqXG4gKiBDb21wYWN0IGZvcm1hdCAodGltZWxpbmUgbWFya2Vycyk6XG4gKiAtIDwgMXM6IHNob3dzIG1pbGxpc2Vjb25kcyAoZS5nLiwgXCIzODBtc1wiKVxuICogLSA8IDFtOiBzaG93cyBzZWNvbmRzIChlLmcuLCBcIjQ1c1wiKVxuICogLSA8IDFoOiBzaG93cyBtaW51dGVzIGFuZCBzZWNvbmRzIChlLmcuLCBcIjJtIDMwc1wiKVxuICogLSA+PSAxaDogc2hvd3MgaG91cnMgYW5kIG1pbnV0ZXMgKGUuZy4sIFwiMmggMzBtXCIpXG4gKlxuICogRnVsbCBmb3JtYXQ6XG4gKiAtIDwgMXM6IHNob3dzIG1pbGxpc2Vjb25kcyAoZS5nLiwgXCIzODBtc1wiKVxuICogLSA8IDFtOiBzaG93cyBzZWNvbmRzIChlLmcuLCBcIjQ1c1wiKVxuICogLSA+PSAxbTogc2hvd3MgZGVjb21wb3NlZCBmb3JtYXQgd2l0aCB3aG9sZSBzZWNvbmRzIChlLmcuLCBcIjFtIDEzc1wiLCBcIjJtIDEzc1wiKVxuICovXG5leHBvcnQgZnVuY3Rpb24gZm9ybWF0RHVyYXRpb24obXM6IG51bWJlciwgY29tcGFjdCA9IGZhbHNlKTogc3RyaW5nIHtcbiAgaWYgKG1zID09PSAwKSB7XG4gICAgcmV0dXJuICcwcyc7XG4gIH1cblxuICBpZiAobXMgPCBNU19JTl9TRUNPTkQpIHtcbiAgICBjb25zdCByb3VuZGVkTXMgPSBNYXRoLnJvdW5kKG1zKTtcbiAgICByZXR1cm4gcm91bmRlZE1zIDwgTVNfSU5fU0VDT05EID8gYCR7cm91bmRlZE1zfW1zYCA6ICcxcyc7XG4gIH1cblxuICBjb25zdCByb3VuZGVkTXMgPSBNYXRoLnJvdW5kKG1zIC8gTVNfSU5fU0VDT05EKSAqIE1TX0lOX1NFQ09ORDtcblxuICBpZiAocm91bmRlZE1zIDwgTVNfSU5fTUlOVVRFKSB7XG4gICAgcmV0dXJuIGAke01hdGguZmxvb3Iocm91bmRlZE1zIC8gTVNfSU5fU0VDT05EKX1zYDtcbiAgfVxuXG4gIC8vIENvbXBhY3QgZm9ybWF0OiBtdWx0aS11bml0IHdpdGhvdXQgZGVjaW1hbHMgKGUuZy4gXCI4bSAyMHNcIiwgXCIyaCAzMG1cIilcbiAgaWYgKGNvbXBhY3QpIHtcbiAgICBpZiAocm91bmRlZE1zIDwgTVNfSU5fSE9VUikge1xuICAgICAgY29uc3QgbSA9IE1hdGguZmxvb3Iocm91bmRlZE1zIC8gTVNfSU5fTUlOVVRFKTtcbiAgICAgIGNvbnN0IHMgPSBNYXRoLmZsb29yKChyb3VuZGVkTXMgJSBNU19JTl9NSU5VVEUpIC8gTVNfSU5fU0VDT05EKTtcbiAgICAgIHJldHVybiBzID4gMCA/IGAke219bSAke3N9c2AgOiBgJHttfW1gO1xuICAgIH1cbiAgICBjb25zdCBoID0gTWF0aC5mbG9vcihyb3VuZGVkTXMgLyBNU19JTl9IT1VSKTtcbiAgICBjb25zdCBtID0gTWF0aC5mbG9vcigocm91bmRlZE1zICUgTVNfSU5fSE9VUikgLyBNU19JTl9NSU5VVEUpO1xuICAgIHJldHVybiBtID4gMCA/IGAke2h9aCAke219bWAgOiBgJHtofWhgO1xuICB9XG5cbiAgLy8gRnVsbCBmb3JtYXQ6IGRlY29tcG9zZSBpbnRvIGxhcmdlciB1bml0cyArIHdob2xlIHNlY29uZHMuXG4gIGNvbnN0IGRheXMgPSBNYXRoLmZsb29yKHJvdW5kZWRNcyAvIE1TX0lOX0RBWSk7XG4gIGNvbnN0IGhvdXJzID0gTWF0aC5mbG9vcigocm91bmRlZE1zICUgTVNfSU5fREFZKSAvIE1TX0lOX0hPVVIpO1xuICBjb25zdCBtaW51dGVzID0gTWF0aC5mbG9vcigocm91bmRlZE1zICUgTVNfSU5fSE9VUikgLyBNU19JTl9NSU5VVEUpO1xuICBjb25zdCBzZWNvbmRzID0gTWF0aC5mbG9vcigocm91bmRlZE1zICUgTVNfSU5fTUlOVVRFKSAvIE1TX0lOX1NFQ09ORCk7XG5cbiAgY29uc3QgcGFydHM6IHN0cmluZ1tdID0gW107XG5cbiAgaWYgKGRheXMgPiAwKSB7XG4gICAgcGFydHMucHVzaChgJHtkYXlzfWRgKTtcbiAgfVxuICBpZiAoaG91cnMgPiAwKSB7XG4gICAgcGFydHMucHVzaChgJHtob3Vyc31oYCk7XG4gIH1cbiAgaWYgKG1pbnV0ZXMgPiAwKSB7XG4gICAgcGFydHMucHVzaChgJHttaW51dGVzfW1gKTtcbiAgfVxuICBwYXJ0cy5wdXNoKGAke3NlY29uZHN9c2ApO1xuXG4gIHJldHVybiBwYXJ0cy5qb2luKCcgJyk7XG59XG5cbi8qKlxuICogUmV0dXJucyBhIGZvcm1hdHRlZCBwYWdpbmF0aW9uIGRpc3BsYXkgc3RyaW5nXG4gKiBAcGFyYW0gY3VycmVudFBhZ2UgLSBUaGUgY3VycmVudCBwYWdlIG51bWJlclxuICogQHBhcmFtIHRvdGFsUGFnZXMgLSBUaGUgdG90YWwgbnVtYmVyIG9mIHBhZ2VzIHZpc2l0ZWQgc28gZmFyXG4gKiBAcGFyYW0gaGFzTW9yZSAtIFdoZXRoZXIgdGhlcmUgYXJlIG1vcmUgcGFnZXMgYXZhaWxhYmxlXG4gKiBAcmV0dXJucyBGb3JtYXR0ZWQgc3RyaW5nIGxpa2UgXCJQYWdlIDEgb2YgMytcIiBvciBcIlBhZ2UgMiBvZiAyXCJcbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGdldFBhZ2luYXRpb25EaXNwbGF5KFxuICBjdXJyZW50UGFnZTogbnVtYmVyLFxuICB0b3RhbFBhZ2VzOiBudW1iZXIsXG4gIGhhc01vcmU6IGJvb2xlYW5cbik6IHN0cmluZyB7XG4gIGlmIChoYXNNb3JlKSB7XG4gICAgcmV0dXJuIGBQYWdlICR7Y3VycmVudFBhZ2V9IG9mICR7dG90YWxQYWdlc30rYDtcbiAgfVxuICByZXR1cm4gYFBhZ2UgJHtjdXJyZW50UGFnZX0gb2YgJHt0b3RhbFBhZ2VzfWA7XG59XG5cbi8vID09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT1cbi8vIER1cmFibGUgQWdlbnQgVXRpbGl0aWVzXG4vLyA9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09XG5cbi8qKlxuICogQ2hlY2sgaWYgYSBzdGVwIGlzIGEgZG9TdHJlYW1TdGVwIChMTE0gY2FsbCB3aXRoIGNvbnZlcnNhdGlvbiBpbnB1dClcbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGlzRG9TdHJlYW1TdGVwKHN0ZXBOYW1lOiBzdHJpbmcpOiBib29sZWFuIHtcbiAgcmV0dXJuIHN0ZXBOYW1lLmVuZHNXaXRoKCcvL2RvU3RyZWFtU3RlcCcpO1xufVxuXG4vKipcbiAqIEV4dHJhY3QgdGhlIGNvbnZlcnNhdGlvbiBmcm9tIGEgaHlkcmF0ZWQgZG9TdHJlYW1TdGVwIGlucHV0LlxuICogZG9TdHJlYW1TdGVwIHNpZ25hdHVyZTogKGNvbnZlcnNhdGlvblByb21wdCwgbW9kZWwsIHdyaXRhYmxlLCB0b29scywgb3B0aW9ucylcbiAqIFNvIGlucHV0WzBdIGlzIHRoZSBjb252ZXJzYXRpb24uXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBleHRyYWN0Q29udmVyc2F0aW9uKHN0ZXBJbnB1dDogdW5rbm93bik6IE1vZGVsTWVzc2FnZVtdIHwgbnVsbCB7XG4gIGlmICghQXJyYXkuaXNBcnJheShzdGVwSW5wdXQpIHx8IHN0ZXBJbnB1dC5sZW5ndGggPT09IDApIHtcbiAgICByZXR1cm4gbnVsbDtcbiAgfVxuXG4gIGNvbnN0IGZpcnN0QXJnID0gc3RlcElucHV0WzBdO1xuXG4gIGlmICghQXJyYXkuaXNBcnJheShmaXJzdEFyZykpIHtcbiAgICByZXR1cm4gbnVsbDtcbiAgfVxuXG4gIC8vIFZhbGlkYXRlIGl0IGxvb2tzIGxpa2UgTW9kZWxNZXNzYWdlW11cbiAgaWYgKFxuICAgICFmaXJzdEFyZy5ldmVyeSgobXNnKSA9PiBtc2cgJiYgdHlwZW9mIG1zZyA9PT0gJ29iamVjdCcgJiYgJ3JvbGUnIGluIG1zZylcbiAgKSB7XG4gICAgcmV0dXJuIG51bGw7XG4gIH1cblxuICByZXR1cm4gZmlyc3RBcmcgYXMgTW9kZWxNZXNzYWdlW107XG59XG5cbi8qKlxuICogQSBkb1N0cmVhbVN0ZXAgd2l0aCBpdHMgY29udmVyc2F0aW9uIGlucHV0IGV4dHJhY3RlZFxuICovXG5leHBvcnQgaW50ZXJmYWNlIFN0cmVhbVN0ZXAge1xuICBzdGVwSWQ6IHN0cmluZztcbiAgc3RlcE5hbWU6IHN0cmluZztcbiAgZGlzcGxheU5hbWU6IHN0cmluZztcbiAgY29udmVyc2F0aW9uOiBNb2RlbE1lc3NhZ2VbXTtcbn1cblxuLyoqXG4gKiBJZGVudGlmaWVzIGFsbCBzdHJlYW0gc3RlcHMgKGRvU3RyZWFtU3RlcCkgaW4gYSBydW4gYW5kIGV4dHJhY3RzIHRoZWlyIGNvbnZlcnNhdGlvbnMuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBpZGVudGlmeVN0cmVhbVN0ZXBzKHN0ZXBzOiBTdGVwW10pOiBTdHJlYW1TdGVwW10ge1xuICByZXR1cm4gc3RlcHNcbiAgICAuZmlsdGVyKChzdGVwKSA9PiBpc0RvU3RyZWFtU3RlcChzdGVwLnN0ZXBOYW1lKSlcbiAgICAubWFwKChzdGVwKSA9PiB7XG4gICAgICBjb25zdCBmdW5jdGlvbk5hbWUgPSBzdGVwLnN0ZXBOYW1lLnNwbGl0KCcvLycpLnBvcCgpID8/ICd1bmtub3duJztcbiAgICAgIGNvbnN0IGNvbnZlcnNhdGlvbiA9IGV4dHJhY3RDb252ZXJzYXRpb24oc3RlcC5pbnB1dCkgPz8gW107XG5cbiAgICAgIHJldHVybiB7XG4gICAgICAgIHN0ZXBJZDogc3RlcC5zdGVwSWQsXG4gICAgICAgIHN0ZXBOYW1lOiBzdGVwLnN0ZXBOYW1lLFxuICAgICAgICBkaXNwbGF5TmFtZTogZnVuY3Rpb25OYW1lLFxuICAgICAgICBjb252ZXJzYXRpb24sXG4gICAgICB9O1xuICAgIH0pO1xufVxuIl19
175
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidXRpbHMuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvbGliL3V0aWxzLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUVBLE9BQU8sRUFBbUIsSUFBSSxFQUFFLE1BQU0sTUFBTSxDQUFDO0FBQzdDLE9BQU8sRUFBRSxPQUFPLEVBQUUsTUFBTSxnQkFBZ0IsQ0FBQztBQUV6QyxNQUFNLFVBQVUsRUFBRSxDQUFDLEdBQUcsTUFBb0I7SUFDeEMsT0FBTyxPQUFPLENBQUMsSUFBSSxDQUFDLE1BQU0sQ0FBQyxDQUFDLENBQUM7QUFDL0IsQ0FBQztBQUVELE1BQU0sWUFBWSxHQUFHLElBQUksQ0FBQztBQUMxQixNQUFNLFlBQVksR0FBRyxFQUFFLEdBQUcsWUFBWSxDQUFDO0FBQ3ZDLE1BQU0sVUFBVSxHQUFHLEVBQUUsR0FBRyxZQUFZLENBQUM7QUFDckMsTUFBTSxTQUFTLEdBQUcsRUFBRSxHQUFHLFVBQVUsQ0FBQztBQUVsQzs7Ozs7Ozs7Ozs7Ozs7Ozs7R0FpQkc7QUFDSCxNQUFNLFVBQVUsY0FBYyxDQUFDLEVBQVUsRUFBRSxPQUFPLEdBQUcsS0FBSztJQUN4RCxJQUFJLEVBQUUsS0FBSyxDQUFDLEVBQUUsQ0FBQztRQUNiLE9BQU8sSUFBSSxDQUFDO0lBQ2QsQ0FBQztJQUVELElBQUksRUFBRSxHQUFHLFlBQVksRUFBRSxDQUFDO1FBQ3RCLE1BQU0sU0FBUyxHQUFHLElBQUksQ0FBQyxLQUFLLENBQUMsRUFBRSxDQUFDLENBQUM7UUFDakMsT0FBTyxTQUFTLEdBQUcsWUFBWSxDQUFDLENBQUMsQ0FBQyxHQUFHLFNBQVMsSUFBSSxDQUFDLENBQUMsQ0FBQyxJQUFJLENBQUM7SUFDNUQsQ0FBQztJQUVELE1BQU0sU0FBUyxHQUFHLElBQUksQ0FBQyxLQUFLLENBQUMsRUFBRSxHQUFHLFlBQVksQ0FBQyxHQUFHLFlBQVksQ0FBQztJQUUvRCxJQUFJLFNBQVMsR0FBRyxZQUFZLEVBQUUsQ0FBQztRQUM3QixPQUFPLEdBQUcsSUFB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QU8sSUFBSSxDQUFDO0lBQ2QsQ0FBQztJQUVELE9BQU8sUUFBMEIsQ0FBQztBQUNwQyxDQUFDO0FBWUQ7O0dBRUc7QUFDSCxNQUFNLFVBQVUsbUJBQW1CLENBQUMsS0FBYTtJQUMvQyxPQUFPLEtBQUs7U0FDVCxNQUFNLENBQUMsQ0FBQyxJQUFJLEVBQUUsRUFBRSxDQUFDLGNBQWMsQ0FBQyxJQUFJLENBQUMsUUFBUSxDQUFDLENBQUM7U0FDL0MsR0FBRyxDQUFDLENBQUMsSUFBSSxFQUFFLEVBQUU7UUFDWixNQUFNLFlBQVksR0FBRyxJQUFJLENBQUMsUUFBUSxDQUFDLEtBQUssQ0FBQyxJQUFJLENBQUMsQ0FBQyxHQUFHLEVBQUUsSUFBSSxTQUFTLENBQUM7UUFDbEUsTUFBTSxZQUFZLEdBQUcsbUJBQW1CLENBQUMsSUFBSSxDQUFDLEtBQUssQ0FBQyxJQUFJLEVBQUUsQ0FBQztRQUUzRCxPQUFPO1lBQ0wsTUFBTSxFQUFFLElBQUksQ0FBQyxNQUFNO1lBQ25CLFFBQVEsRUFBRSxJQUFJLENBQUMsUUFBUTtZQUN2QixXQUFXLEVBQUUsWUFBWTtZQUN6QixZQUFZO1NBQ2IsQ0FBQztJQUNKLENBQUMsQ0FBQyxDQUFDO0FBQ1AsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB0eXBlIHsgU3RlcCB9IGZyb20gJ0B3b3JrZmxvdy93b3JsZCc7XG5pbXBvcnQgdHlwZSB7IE1vZGVsTWVzc2FnZSB9IGZyb20gJ2FpJztcbmltcG9ydCB7IHR5cGUgQ2xhc3NWYWx1ZSwgY2xzeCB9IGZyb20gJ2Nsc3gnO1xuaW1wb3J0IHsgdHdNZXJnZSB9IGZyb20gJ3RhaWx3aW5kLW1lcmdlJztcblxuZXhwb3J0IGZ1bmN0aW9uIGNuKC4uLmlucHV0czogQ2xhc3NWYWx1ZVtdKSB7XG4gIHJldHVybiB0d01lcmdlKGNsc3goaW5wdXRzKSk7XG59XG5cbmNvbnN0IE1TX0lOX1NFQ09ORCA9IDEwMDA7XG5jb25zdCBNU19JTl9NSU5VVEUgPSA2MCAqIE1TX0lOX1NFQ09ORDtcbmNvbnN0IE1TX0lOX0hPVVIgPSA2MCAqIE1TX0lOX01JTlVURTtcbmNvbnN0IE1TX0lOX0RBWSA9IDI0ICogTVNfSU5fSE9VUjtcblxuLyoqXG4gKiBGb3JtYXRzIGEgZHVyYXRpb24gaW4gbWlsbGlzZWNvbmRzIHRvIGEgaHVtYW4tcmVhZGFibGUgc3RyaW5nLlxuICpcbiAqIEBwYXJhbSBtcyAtIER1cmF0aW9uIGluIG1pbGxpc2Vjb25kc1xuICogQHBhcmFtIGNvbXBhY3QgLSBJZiB0cnVlLCByZXR1cm5zIGEgY29tcGFjdCBmb3JtYXQgKGUuZy4sIFwiMzgwbXNcIiwgXCIybSAzMHNcIikuXG4gKiAgICAgICAgICAgICAgICAgIElmIGZhbHNlIChkZWZhdWx0KSwgcmV0dXJucyBtdWx0aS1wYXJ0IGZvcm1hdCAoZS5nLiwgXCIxbSAxM3NcIiwgXCIyZCA1aCAzbSAxMnNcIikuXG4gKlxuICogQ29tcGFjdCBmb3JtYXQgKHRpbWVsaW5lIG1hcmtlcnMpOlxuICogLSA8IDFzOiBzaG93cyBtaWxsaXNlY29uZHMgKGUuZy4sIFwiMzgwbXNcIilcbiAqIC0gPCAxbTogc2hvd3Mgc2Vjb25kcyAoZS5nLiwgXCI0NXNcIilcbiAqIC0gPCAxaDogc2hvd3MgbWludXRlcyBhbmQgc2Vjb25kcyAoZS5nLiwgXCIybSAzMHNcIilcbiAqIC0gPj0gMWg6IHNob3dzIGhvdXJzIGFuZCBtaW51dGVzIChlLmcuLCBcIjJoIDMwbVwiKVxuICpcbiAqIEZ1bGwgZm9ybWF0OlxuICogLSA8IDFzOiBzaG93cyBtaWxsaXNlY29uZHMgKGUuZy4sIFwiMzgwbXNcIilcbiAqIC0gPCAxbTogc2hvd3Mgc2Vjb25kcyAoZS5nLiwgXCI0NXNcIilcbiAqIC0gPj0gMW06IHNob3dzIGRlY29tcG9zZWQgZm9ybWF0IHdpdGggd2hvbGUgc2Vjb25kcyAoZS5nLiwgXCIxbSAxM3NcIiwgXCIybSAxM3NcIilcbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGZvcm1hdER1cmF0aW9uKG1zOiBudW1iZXIsIGNvbXBhY3QgPSBmYWxzZSk6IHN0cmluZyB7XG4gIGlmIChtcyA9PT0gMCkge1xuICAgIHJldHVybiAnMHMnO1xuICB9XG5cbiAgaWYgKG1zIDwgTVNfSU5fU0VDT05EKSB7XG4gICAgY29uc3Qgcm91bmRlZE1zID0gTWF0aC5yb3VuZChtcyk7XG4gICAgcmV0dXJuIHJvdW5kZWRNcyA8IE1TX0lOX1NFQ09ORCA/IGAke3JvdW5kZWRNc31tc2AgOiAnMXMnO1xuICB9XG5cbiAgY29uc3Qgcm91bmRlZE1zID0gTWF0aC5yb3VuZChtcyAvIE1TX0lOX1NFQ09ORCkgKiBNU19JTl9TRUNPTkQ7XG5cbiAgaWYgKHJvdW5kZWRNcyA8IE1TX0lOX01JTlVURSkge1xuICAgIHJldHVybiBgJHtNYXRoLmZsb29yKHJvdW5kZWRNcyAvIE1TX0lOX1NFQ09ORCl9c2A7XG4gIH1cblxuICAvLyBDb21wYWN0IGZvcm1hdDogbXVsdGktdW5pdCB3aXRob3V0IGRlY2ltYWxzIChlLmcuIFwiOG0gMjBzXCIsIFwiMmggMzBtXCIpXG4gIGlmIChjb21wYWN0KSB7XG4gICAgaWYgKHJvdW5kZWRNcyA8IE1TX0lOX0hPVVIpIHtcbiAgICAgIGNvbnN0IG0gPSBNYXRoLmZsb29yKHJvdW5kZWRNcyAvIE1TX0lOX01JTlVURSk7XG4gICAgICBjb25zdCBzID0gTWF0aC5mbG9vcigocm91bmRlZE1zICUgTVNfSU5fTUlOVVRFKSAvIE1TX0lOX1NFQ09ORCk7XG4gICAgICByZXR1cm4gcyA+IDAgPyBgJHttfW0gJHtzfXNgIDogYCR7bX1tYDtcbiAgICB9XG4gICAgY29uc3QgaCA9IE1hdGguZmxvb3Iocm91bmRlZE1zIC8gTVNfSU5fSE9VUik7XG4gICAgY29uc3QgbSA9IE1hdGguZmxvb3IoKHJvdW5kZWRNcyAlIE1TX0lOX0hPVVIpIC8gTVNfSU5fTUlOVVRFKTtcbiAgICByZXR1cm4gbSA+IDAgPyBgJHtofWggJHttfW1gIDogYCR7aH1oYDtcbiAgfVxuXG4gIC8vIEZ1bGwgZm9ybWF0OiBkZWNvbXBvc2UgaW50byBsYXJnZXIgdW5pdHMgKyB3aG9sZSBzZWNvbmRzLlxuICBjb25zdCBkYXlzID0gTWF0aC5mbG9vcihyb3VuZGVkTXMgLyBNU19JTl9EQVkpO1xuICBjb25zdCBob3VycyA9IE1hdGguZmxvb3IoKHJvdW5kZWRNcyAlIE1TX0lOX0RBWSkgLyBNU19JTl9IT1VSKTtcbiAgY29uc3QgbWludXRlcyA9IE1hdGguZmxvb3IoKHJvdW5kZWRNcyAlIE1TX0lOX0hPVVIpIC8gTVNfSU5fTUlOVVRFKTtcbiAgY29uc3Qgc2Vjb25kcyA9IE1hdGguZmxvb3IoKHJvdW5kZWRNcyAlIE1TX0lOX01JTlVURSkgLyBNU19JTl9TRUNPTkQpO1xuXG4gIGNvbnN0IHBhcnRzOiBzdHJpbmdbXSA9IFtdO1xuXG4gIGlmIChkYXlzID4gMCkge1xuICAgIHBhcnRzLnB1c2goYCR7ZGF5c31kYCk7XG4gIH1cbiAgaWYgKGhvdXJzID4gMCkge1xuICAgIHBhcnRzLnB1c2goYCR7aG91cnN9aGApO1xuICB9XG4gIGlmIChtaW51dGVzID4gMCkge1xuICAgIHBhcnRzLnB1c2goYCR7bWludXRlc31tYCk7XG4gIH1cbiAgcGFydHMucHVzaChgJHtzZWNvbmRzfXNgKTtcblxuICByZXR1cm4gcGFydHMuam9pbignICcpO1xufVxuXG4vLyBMb2NhbGUtYXdhcmUgZm9ybWF0dGVyIHRoYXQgYWx3YXlzIHJlbmRlcnMgZXhhY3RseSB0d28gZnJhY3Rpb24gZGlnaXRzLlxuLy8gVXNlZCBmb3IgdGhlIHNlY29uZHMgY29tcG9uZW50IG9mIHByZWNpc2UgZHVyYXRpb25zIHNvIHZhbHVlcyBhcmUgbmV2ZXJcbi8vIHNuYXBwZWQgdG8gYSB3aG9sZSBzZWNvbmQgKGFuZCB0aG91c2FuZHMgc2VwYXJhdG9ycyBzdGF5IGNvcnJlY3QpLlxuY29uc3QgcHJlY2lzZVNlY29uZHNGb3JtYXR0ZXIgPSBuZXcgSW50bC5OdW1iZXJGb3JtYXQodW5kZWZpbmVkLCB7XG4gIG1pbmltdW1GcmFjdGlvbkRpZ2l0czogMixcbiAgbWF4aW11bUZyYWN0aW9uRGlnaXRzOiAyLFxufSk7XG5cbi8qKlxuICogRm9ybWF0cyBhIGR1cmF0aW9uIGluIG1pbGxpc2Vjb25kcyB3aXRob3V0IHNuYXBwaW5nIHRvIHdob2xlIHNlY29uZHMuXG4gKlxuICogVW5saWtlIHtAbGluayBmb3JtYXREdXJhdGlvbn0sIHRoaXMgbmV2ZXIgcm91bmRzIGEgc3ViLW1pbnV0ZSB2YWx1ZSB1cCB0b1xuICogdGhlIG5leHQgd2hvbGUgc2Vjb25kIChlLmcuIDE2MjZtcyByZW5kZXJzIGFzIFwiMS42M3NcIiwgbm90IFwiMnNcIikuXG4gKlxuICogLSA8IDFzOiBzaG93cyB3aG9sZSBtaWxsaXNlY29uZHMgKGUuZy4gXCI2MjZtc1wiKVxuICogLSAxcyDigJMgMW06IHNob3dzIHNlY29uZHMgd2l0aCB0d28gZGVjaW1hbHMgKGUuZy4gXCIxLjYzc1wiLCBcIjQ1LjIwc1wiKVxuICogLSA+PSAxbTogZGVjb21wb3NlcyBpbnRvIGQvaC9tIHdpdGggdHdvLWRlY2ltYWwgc2Vjb25kcyAoZS5nLiBcIjFtIDEzLjQ1c1wiKVxuICovXG5leHBvcnQgZnVuY3Rpb24gZm9ybWF0RHVyYXRpb25QcmVjaXNlKG1zOiBudW1iZXIpOiBzdHJpbmcge1xuICBpZiAobXMgPT09IDApIHtcbiAgICByZXR1cm4gJzBzJztcbiAgfVxuXG4gIGlmIChtcyA8IE1TX0lOX1NFQ09ORCkge1xuICAgIGNvbnN0IHJvdW5kZWRNcyA9IE1hdGgucm91bmQobXMpO1xuICAgIGlmIChyb3VuZGVkTXMgPCBNU19JTl9TRUNPTkQpIHtcbiAgICAgIHJldHVybiBgJHtyb3VuZGVkTXN9bXNgO1xuICAgIH1cbiAgfVxuXG4gIGNvbnN0IG5vcm1hbGl6ZWRNcyA9IE1hdGgucm91bmQobXMgLyAxMCkgKiAxMDtcblxuICBpZiAobm9ybWFsaXplZE1zIDwgTVNfSU5fTUlOVVRFKSB7XG4gICAgcmV0dXJuIGAke3ByZWNpc2VTZWNvbmRzRm9ybWF0dGVyLmZvcm1hdChub3JtYWxpemVkTXMgLyBNU19JTl9TRUNPTkQpfXNgO1xuICB9XG5cbiAgY29uc3QgZGF5cyA9IE1hdGguZmxvb3Iobm9ybWFsaXplZE1zIC8gTVNfSU5fREFZKTtcbiAgY29uc3QgaG91cnMgPSBNYXRoLmZsb29yKChub3JtYWxpemVkTXMgJSBNU19JTl9EQVkpIC8gTVNfSU5fSE9VUik7XG4gIGNvbnN0IG1pbnV0ZXMgPSBNYXRoLmZsb29yKChub3JtYWxpemVkTXMgJSBNU19JTl9IT1VSKSAvIE1TX0lOX01JTlVURSk7XG4gIGNvbnN0IHNlY29uZHMgPSAobm9ybWFsaXplZE1zICUgTVNfSU5fTUlOVVRFKSAvIE1TX0lOX1NFQ09ORDtcblxuICBjb25zdCBwYXJ0czogc3RyaW5nW10gPSBbXTtcblxuICBpZiAoZGF5cyA+IDApIHtcbiAgICBwYXJ0cy5wdXNoKGAke2RheXN9ZGApO1xuICB9XG4gIGlmIChob3VycyA+IDApIHtcbiAgICBwYXJ0cy5wdXNoKGAke2hvdXJzfWhgKTtcbiAgfVxuICBpZiAobWludXRlcyA+IDApIHtcbiAgICBwYXJ0cy5wdXNoKGAke21pbnV0ZXN9bWApO1xuICB9XG4gIHBhcnRzLnB1c2goYCR7cHJlY2lzZVNlY29uZHNGb3JtYXR0ZXIuZm9ybWF0KHNlY29uZHMpfXNgKTtcblxuICByZXR1cm4gcGFydHMuam9pbignICcpO1xufVxuXG4vKipcbiAqIFJldHVybnMgYSBmb3JtYXR0ZWQgcGFnaW5hdGlvbiBkaXNwbGF5IHN0cmluZ1xuICogQHBhcmFtIGN1cnJlbnRQYWdlIC0gVGhlIGN1cnJlbnQgcGFnZSBudW1iZXJcbiAqIEBwYXJhbSB0b3RhbFBhZ2VzIC0gVGhlIHRvdGFsIG51bWJlciBvZiBwYWdlcyB2aXNpdGVkIHNvIGZhclxuICogQHBhcmFtIGhhc01vcmUgLSBXaGV0aGVyIHRoZXJlIGFyZSBtb3JlIHBhZ2VzIGF2YWlsYWJsZVxuICogQHJldHVybnMgRm9ybWF0dGVkIHN0cmluZyBsaWtlIFwiUGFnZSAxIG9mIDMrXCIgb3IgXCJQYWdlIDIgb2YgMlwiXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBnZXRQYWdpbmF0aW9uRGlzcGxheShcbiAgY3VycmVudFBhZ2U6IG51bWJlcixcbiAgdG90YWxQYWdlczogbnVtYmVyLFxuICBoYXNNb3JlOiBib29sZWFuXG4pOiBzdHJpbmcge1xuICBpZiAoaGFzTW9yZSkge1xuICAgIHJldHVybiBgUGFnZSAke2N1cnJlbnRQYWdlfSBvZiAke3RvdGFsUGFnZXN9K2A7XG4gIH1cbiAgcmV0dXJuIGBQYWdlICR7Y3VycmVudFBhZ2V9IG9mICR7dG90YWxQYWdlc31gO1xufVxuXG4vLyA9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09XG4vLyBEdXJhYmxlIEFnZW50IFV0aWxpdGllc1xuLy8gPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PVxuXG4vKipcbiAqIENoZWNrIGlmIGEgc3RlcCBpcyBhIGRvU3RyZWFtU3RlcCAoTExNIGNhbGwgd2l0aCBjb252ZXJzYXRpb24gaW5wdXQpXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBpc0RvU3RyZWFtU3RlcChzdGVwTmFtZTogc3RyaW5nKTogYm9vbGVhbiB7XG4gIHJldHVybiBzdGVwTmFtZS5lbmRzV2l0aCgnLy9kb1N0cmVhbVN0ZXAnKTtcbn1cblxuLyoqXG4gKiBFeHRyYWN0IHRoZSBjb252ZXJzYXRpb24gZnJvbSBhIGh5ZHJhdGVkIGRvU3RyZWFtU3RlcCBpbnB1dC5cbiAqIGRvU3RyZWFtU3RlcCBzaWduYXR1cmU6IChjb252ZXJzYXRpb25Qcm9tcHQsIG1vZGVsLCB3cml0YWJsZSwgdG9vbHMsIG9wdGlvbnMpXG4gKiBTbyBpbnB1dFswXSBpcyB0aGUgY29udmVyc2F0aW9uLlxuICovXG5leHBvcnQgZnVuY3Rpb24gZXh0cmFjdENvbnZlcnNhdGlvbihzdGVwSW5wdXQ6IHVua25vd24pOiBNb2RlbE1lc3NhZ2VbXSB8IG51bGwge1xuICBpZiAoIUFycmF5LmlzQXJyYXkoc3RlcElucHV0KSB8fCBzdGVwSW5wdXQubGVuZ3RoID09PSAwKSB7XG4gICAgcmV0dXJuIG51bGw7XG4gIH1cblxuICBjb25zdCBmaXJzdEFyZyA9IHN0ZXBJbnB1dFswXTtcblxuICBpZiAoIUFycmF5LmlzQXJyYXkoZmlyc3RBcmcpKSB7XG4gICAgcmV0dXJuIG51bGw7XG4gIH1cblxuICAvLyBWYWxpZGF0ZSBpdCBsb29rcyBsaWtlIE1vZGVsTWVzc2FnZVtdXG4gIGlmIChcbiAgICAhZmlyc3RBcmcuZXZlcnkoKG1zZykgPT4gbXNnICYmIHR5cGVvZiBtc2cgPT09ICdvYmplY3QnICYmICdyb2xlJyBpbiBtc2cpXG4gICkge1xuICAgIHJldHVybiBudWxsO1xuICB9XG5cbiAgcmV0dXJuIGZpcnN0QXJnIGFzIE1vZGVsTWVzc2FnZVtdO1xufVxuXG4vKipcbiAqIEEgZG9TdHJlYW1TdGVwIHdpdGggaXRzIGNvbnZlcnNhdGlvbiBpbnB1dCBleHRyYWN0ZWRcbiAqL1xuZXhwb3J0IGludGVyZmFjZSBTdHJlYW1TdGVwIHtcbiAgc3RlcElkOiBzdHJpbmc7XG4gIHN0ZXBOYW1lOiBzdHJpbmc7XG4gIGRpc3BsYXlOYW1lOiBzdHJpbmc7XG4gIGNvbnZlcnNhdGlvbjogTW9kZWxNZXNzYWdlW107XG59XG5cbi8qKlxuICogSWRlbnRpZmllcyBhbGwgc3RyZWFtIHN0ZXBzIChkb1N0cmVhbVN0ZXApIGluIGEgcnVuIGFuZCBleHRyYWN0cyB0aGVpciBjb252ZXJzYXRpb25zLlxuICovXG5leHBvcnQgZnVuY3Rpb24gaWRlbnRpZnlTdHJlYW1TdGVwcyhzdGVwczogU3RlcFtdKTogU3RyZWFtU3RlcFtdIHtcbiAgcmV0dXJuIHN0ZXBzXG4gICAgLmZpbHRlcigoc3RlcCkgPT4gaXNEb1N0cmVhbVN0ZXAoc3RlcC5zdGVwTmFtZSkpXG4gICAgLm1hcCgoc3RlcCkgPT4ge1xuICAgICAgY29uc3QgZnVuY3Rpb25OYW1lID0gc3RlcC5zdGVwTmFtZS5zcGxpdCgnLy8nKS5wb3AoKSA/PyAndW5rbm93bic7XG4gICAgICBjb25zdCBjb252ZXJzYXRpb24gPSBleHRyYWN0Q29udmVyc2F0aW9uKHN0ZXAuaW5wdXQpID8/IFtdO1xuXG4gICAgICByZXR1cm4ge1xuICAgICAgICBzdGVwSWQ6IHN0ZXAuc3RlcElkLFxuICAgICAgICBzdGVwTmFtZTogc3RlcC5zdGVwTmFtZSxcbiAgICAgICAgZGlzcGxheU5hbWU6IGZ1bmN0aW9uTmFtZSxcbiAgICAgICAgY29udmVyc2F0aW9uLFxuICAgICAgfTtcbiAgICB9KTtcbn1cbiJdfQ==
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.13",
4
+ "version": "5.0.0-beta.15",
5
5
  "private": false,
6
6
  "files": [
7
7
  "dist",
@@ -53,9 +53,9 @@
53
53
  "streamdown": "2.3.0",
54
54
  "tailwind-merge": "3.5.0",
55
55
  "tailwindcss": "4",
56
- "@workflow/core": "5.0.0-beta.13",
56
+ "@workflow/core": "5.0.0-beta.15",
57
57
  "@workflow/utils": "5.0.0-beta.3",
58
- "@workflow/world": "5.0.0-beta.7"
58
+ "@workflow/world": "5.0.0-beta.9"
59
59
  },
60
60
  "devDependencies": {
61
61
  "@biomejs/biome": "^2.4.4",
@@ -69,7 +69,7 @@
69
69
  "@workflow/tsconfig": "5.0.0-beta.0"
70
70
  },
71
71
  "scripts": {
72
- "build": "tsc && cp -r src/components/trace-viewer/*.css dist/components/trace-viewer/ && cp src/styles.css dist/styles.css",
72
+ "build": "tsc && cp -r src/components/trace-viewer/*.css dist/components/trace-viewer/ && cp src/components/new-trace-viewer/components/*.module.css dist/components/new-trace-viewer/components/ && cp src/styles.css dist/styles.css",
73
73
  "dev": "tsc --watch",
74
74
  "clean": "tsc --build --clean && rm -r dist ||:",
75
75
  "test": "vitest run",
@@ -2,7 +2,7 @@ import { Circle } from 'lucide-react';
2
2
  import { useRef } from 'react';
3
3
  import { cn } from '../../../lib/utils';
4
4
  import type { Span } from '../types';
5
- import { formatDuration } from '../../trace-viewer/util/timing';
5
+ import { formatDurationPrecise } from '../../trace-viewer/util/timing';
6
6
  import {
7
7
  SleepIcon,
8
8
  StepForwardIcon,
@@ -86,7 +86,7 @@ const EventRow = ({
86
86
  </div>
87
87
  <div className="ml-2 shrink-0">
88
88
  <span className="text-label-14 text-gray-900 tabular-nums">
89
- {formatDuration(durationMs)}
89
+ {formatDurationPrecise(durationMs)}
90
90
  </span>
91
91
  </div>
92
92
  </div>
@@ -0,0 +1,31 @@
1
+ .runningStripes {
2
+ position: absolute;
3
+ inset: 0;
4
+ pointer-events: none;
5
+ border-radius: 0.25rem;
6
+ opacity: 0.5;
7
+ background-image: repeating-linear-gradient(
8
+ 45deg,
9
+ var(--ds-blue-500) 0,
10
+ var(--ds-blue-500) 1.5px,
11
+ transparent 1.5px,
12
+ transparent 8px
13
+ );
14
+ background-size: 11.314px 11.314px;
15
+ animation: running-stripes 0.6s linear infinite;
16
+ }
17
+
18
+ @keyframes running-stripes {
19
+ from {
20
+ background-position: 0 0;
21
+ }
22
+ to {
23
+ background-position: 11.314px 0;
24
+ }
25
+ }
26
+
27
+ @media (prefers-reduced-motion: reduce) {
28
+ .runningStripes {
29
+ animation: none;
30
+ }
31
+ }
@@ -5,7 +5,11 @@ import type { CSSProperties, ReactNode } from 'react';
5
5
  import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react';
6
6
  import { cn } from '../../../lib/utils';
7
7
  import type { Span } from '../types';
8
- import { formatDuration, getHighResInMs } from '../../trace-viewer/util/timing';
8
+ import {
9
+ formatDuration,
10
+ formatDurationPrecise,
11
+ getHighResInMs,
12
+ } from '../../trace-viewer/util/timing';
9
13
  import { isSpanDimmedBySearch, type SpanSearchResult } from '../search';
10
14
  import type { Segment, SegmentStatus, TimeMarker } from '../utils';
11
15
  import {
@@ -15,6 +19,7 @@ import {
15
19
  getSpanDurationMs,
16
20
  } from '../utils';
17
21
  import { ROW_HEIGHT_PX, useRowWindow } from './use-row-window';
22
+ import styles from './timeline.module.css';
18
23
 
19
24
  // ---------------------------------------------------------------------------
20
25
  // Constants
@@ -29,6 +34,7 @@ const SEGMENT_CLASSES: Record<SegmentStatus, string> = {
29
34
  retrying: 'bg-gray-400 border border-gray-500',
30
35
  waiting: 'bg-gray-400 border border-gray-500',
31
36
  running: 'bg-blue-200 border border-blue-500',
37
+ completed: 'bg-blue-200 border border-blue-500',
32
38
  failed: 'bg-red-200 border border-red-500',
33
39
  succeeded: 'bg-green-200 border border-green-500',
34
40
  sleeping: 'bg-gray-400 border border-gray-500',
@@ -40,6 +46,15 @@ const TIMELINE_INSET_STYLE: CSSProperties = {
40
46
  right: TIMELINE_PADDING_PX,
41
47
  };
42
48
 
49
+ const ACTIVE_SEGMENT_STATUSES: ReadonlySet<SegmentStatus> = new Set([
50
+ 'running',
51
+ 'received',
52
+ ]);
53
+
54
+ function RunningStripes(): ReactNode {
55
+ return <div aria-hidden className={styles.runningStripes} />;
56
+ }
57
+
43
58
  // ---------------------------------------------------------------------------
44
59
  // Bar geometry
45
60
  // ---------------------------------------------------------------------------
@@ -218,25 +233,57 @@ function PlainBar({
218
233
  );
219
234
  }
220
235
 
236
+ function LeadInConnector({
237
+ leftPct,
238
+ widthPct,
239
+ label,
240
+ }: {
241
+ leftPct: number;
242
+ widthPct: number;
243
+ label: string | null;
244
+ }): ReactNode {
245
+ return (
246
+ <div
247
+ className="absolute top-1/2 h-6 -translate-y-1/2"
248
+ style={{
249
+ left: `calc(${leftPct}% + 0.5px)`,
250
+ width: `calc(${widthPct}% - 1px)`,
251
+ }}
252
+ >
253
+ <div className="absolute left-0 top-1/2 h-4 w-px -translate-y-1/2 bg-gray-500" />
254
+ <div className="absolute inset-x-0 top-1/2 h-px -translate-y-1/2 bg-gray-500" />
255
+ {label ? <DurationLabel label={label} /> : null}
256
+ </div>
257
+ );
258
+ }
259
+
221
260
  function SegmentBar({ segments }: { segments: VisibleSegment[] }): ReactNode {
222
261
  return (
223
262
  <div className="relative h-6 w-full">
224
263
  {segments.map((seg, i) => {
225
- const label = formatDuration(seg.fullDurationMs);
264
+ if (seg.status === 'queued') {
265
+ const leadInLabel = formatDurationPrecise(seg.fullDurationMs);
266
+ const showLeadInLabel =
267
+ seg.pixelWidth >= Math.max(40, leadInLabel.length * 6 + 12);
268
+ return (
269
+ <LeadInConnector
270
+ key={i}
271
+ leftPct={seg.leftPct}
272
+ widthPct={seg.widthPct}
273
+ label={showLeadInLabel ? leadInLabel : null}
274
+ />
275
+ );
276
+ }
277
+
278
+ const label = formatDurationPrecise(seg.fullDurationMs);
226
279
  // Only render the label when there's enough room for it without clipping.
227
280
  const showLabel = seg.pixelWidth >= Math.max(40, label.length * 6 + 12);
228
- // Beef up the queued segment when it's too narrow to read.
229
- const isNarrowQueued = seg.status === 'queued' && seg.pixelWidth < 20;
230
- const overrideBg = isNarrowQueued ? 'var(--ds-gray-400)' : undefined;
231
- const overrideBorder = isNarrowQueued
232
- ? 'var(--ds-gray-500)'
233
- : undefined;
234
281
 
235
282
  return (
236
283
  <div
237
284
  key={i}
238
285
  className={cn(
239
- 'absolute h-full rounded-[0.25rem]',
286
+ 'absolute h-full overflow-hidden rounded-[0.25rem]',
240
287
  SEGMENT_CLASSES[seg.status]
241
288
  )}
242
289
  style={{
@@ -244,10 +291,11 @@ function SegmentBar({ segments }: { segments: VisibleSegment[] }): ReactNode {
244
291
  left: `calc(${seg.leftPct}% + 0.5px)`,
245
292
  width: `calc(${seg.widthPct}% - 1px)`,
246
293
  minWidth: 1,
247
- background: overrideBg,
248
- borderColor: overrideBorder,
249
294
  }}
250
295
  >
296
+ {ACTIVE_SEGMENT_STATUSES.has(seg.status) ? (
297
+ <RunningStripes />
298
+ ) : null}
251
299
  {showLabel ? <DurationLabel label={label} /> : null}
252
300
  </div>
253
301
  );
@@ -313,7 +361,7 @@ const TimelineBar = memo(function TimelineBar({
313
361
  ? (colors.errorBorder ?? 'var(--ds-red-500)')
314
362
  : colors.border;
315
363
 
316
- const totalLabel = formatDuration(totalDurationMs);
364
+ const totalLabel = formatDurationPrecise(totalDurationMs);
317
365
  const showTotalLabel =
318
366
  geometry.visiblePixelWidth >= Math.max(40, totalLabel.length * 6 + 12);
319
367
 
@@ -185,6 +185,7 @@ export function getResourceColor(resource: string): {
185
185
  export type SegmentStatus =
186
186
  | 'queued'
187
187
  | 'running'
188
+ | 'completed'
188
189
  | 'failed'
189
190
  | 'retrying'
190
191
  | 'succeeded'
@@ -373,6 +374,12 @@ function computeSleepSegmentsFromSpan(
373
374
  return [{ startFraction: 0, endFraction: 1, status: 'sleeping' }];
374
375
  }
375
376
 
377
+ function runSegmentStatus(runStatus: string | undefined): SegmentStatus {
378
+ if (runStatus === 'failed') return 'failed';
379
+ if (runStatus === 'running' || runStatus === 'pending') return 'running';
380
+ return 'completed';
381
+ }
382
+
376
383
  function computeRunSegmentsFromSpan(
377
384
  startMs: number,
378
385
  duration: number,
@@ -387,11 +394,12 @@ function computeRunSegmentsFromSpan(
387
394
  .map((e) => ({ name: e.name, time: getHighResInMs(e.timestamp) }))
388
395
  .sort((a, b) => a.time - b.time);
389
396
 
397
+ const runData = attributes?.data as Record<string, unknown> | undefined;
398
+ const runStatus = runData?.status as string | undefined;
399
+
390
400
  const hasRunCreated = sorted.some((e) => e.name === 'run_created');
391
401
 
392
402
  if (!hasRunCreated) {
393
- const runData = attributes?.data as Record<string, unknown> | undefined;
394
- const runStatus = runData?.status as string | undefined;
395
403
  return computeV1RunSegments(startMs, duration, activeStartMs, runStatus);
396
404
  }
397
405
 
@@ -413,7 +421,7 @@ function computeRunSegmentsFromSpan(
413
421
  segments.push({
414
422
  startFraction: cursor,
415
423
  endFraction: 1,
416
- status: failedEvent ? 'failed' : 'running',
424
+ status: failedEvent ? 'failed' : runSegmentStatus(runStatus),
417
425
  });
418
426
 
419
427
  return segments;
@@ -443,7 +451,7 @@ function computeV1RunSegments(
443
451
  segments.push({
444
452
  startFraction: cursor,
445
453
  endFraction: 1,
446
- status: runStatus === 'failed' ? 'failed' : 'running',
454
+ status: runSegmentStatus(runStatus),
447
455
  });
448
456
 
449
457
  return segments;
@@ -261,6 +261,7 @@ const attributeOrder: AttributeKey[] = [
261
261
  'eventData',
262
262
  'input',
263
263
  'output',
264
+ 'attributes',
264
265
  'resumeAt',
265
266
  ];
266
267
 
@@ -284,6 +285,7 @@ const attributeDisplayNames: Partial<Record<AttributeKey, string>> = {
284
285
  runId: 'Run ID',
285
286
  token: 'Token',
286
287
  eventType: 'Event Type',
288
+ errorCode: 'Error Code',
287
289
  correlationId: 'Correlation ID',
288
290
  deploymentId: 'Deployment ID',
289
291
  specVersion: 'Spec Version',
@@ -415,11 +417,11 @@ const attributeToDisplayFn: Record<
415
417
  environment: (_value: unknown) => null,
416
418
  executionContext: (_value: unknown) => null,
417
419
  // Attributes MVP — string-string metadata attached to the run.
418
- // Rendered as a JSON block; if empty/missing, hidden by the
419
- // hasDisplayContent gate above.
420
+ // Rendered in its own collapsible DetailCard; if empty/missing, hidden
421
+ // by the hasDisplayContent gate.
420
422
  attributes: (value: unknown) => {
421
423
  if (!hasDisplayContent(value)) return null;
422
- return JsonBlock(value);
424
+ return <DetailCard summary="Attributes">{JsonBlock(value)}</DetailCard>;
423
425
  },
424
426
  // Dates — wrapped with TimestampTooltip showing UTC/local + relative time
425
427
  createdAt: timestampWithTooltipOrNull,
@@ -588,6 +590,7 @@ const resolvableAttributes = [
588
590
  'output',
589
591
  'error',
590
592
  'metadata',
593
+ 'attributes',
591
594
  'eventData',
592
595
  ];
593
596
 
@@ -597,6 +600,7 @@ const selfHeaderedAttributes = new Set([
597
600
  'input',
598
601
  'output',
599
602
  'error',
603
+ 'attributes',
600
604
  'eventData',
601
605
  ]);
602
606
 
@@ -10,6 +10,10 @@ import { AttributePanel } from './attribute-panel';
10
10
  import { EventsList } from './events-list';
11
11
  import { ResolveHookModal } from './resolve-hook-modal';
12
12
  import { useSidebarDataOptional } from './sidebar-data-context';
13
+ import {
14
+ mergeSpanDetail,
15
+ spanDetailMatchesSelection,
16
+ } from './span-detail-merge';
13
17
 
14
18
  // Type guards for runtime validation of span attribute data
15
19
  function isStep(data: unknown): data is Step {
@@ -215,19 +219,27 @@ export function EntityDetailPanel({
215
219
  const error = spanDetailError ?? undefined;
216
220
  const loading = spanDetailLoading ?? false;
217
221
 
222
+ const matchedSpanDetailData = useMemo(
223
+ () =>
224
+ spanDetailMatchesSelection(spanDetailData, resource, resourceId)
225
+ ? spanDetailData
226
+ : null,
227
+ [spanDetailData, resource, resourceId]
228
+ );
229
+
218
230
  // Get the hook token for resolving (prefer fetched data, then hooks array fallback)
219
231
  const hookToken = useMemo(() => {
220
232
  if (resource !== 'hook' || !resourceId) return undefined;
221
233
  // 1. Try the externally-fetched detail data first
222
- if (isHook(spanDetailData) && spanDetailData.token) {
223
- return spanDetailData.token;
234
+ if (isHook(matchedSpanDetailData) && matchedSpanDetailData.token) {
235
+ return matchedSpanDetailData.token;
224
236
  }
225
237
  // 2. Try the span's inline data (reconstructed from hook_created event)
226
238
  if (isHook(data) && (data as Hook).token) {
227
239
  return (data as Hook).token;
228
240
  }
229
241
  return undefined;
230
- }, [resource, resourceId, spanDetailData, data]);
242
+ }, [resource, resourceId, matchedSpanDetailData, data]);
231
243
 
232
244
  useEffect(() => {
233
245
  if (error && selectedSpan && resource) {
@@ -289,7 +301,7 @@ export function EntityDetailPanel({
289
301
 
290
302
  try {
291
303
  setResolvingHook(true);
292
- const candidate = spanDetailData ?? data;
304
+ const candidate = matchedSpanDetailData ?? data;
293
305
  const hook = isHook(candidate) ? candidate : undefined;
294
306
  await onResolveHook(hookToken, payload, hook);
295
307
  toast.success('Hook resolved', {
@@ -310,17 +322,18 @@ export function EntityDetailPanel({
310
322
  setResolvingHook(false);
311
323
  }
312
324
  },
313
- [onResolveHook, hookToken, resolvingHook, spanDetailData, data]
325
+ [onResolveHook, hookToken, resolvingHook, matchedSpanDetailData, data]
314
326
  );
315
327
 
316
- // Prefer externally-fetched details when available. For sleep spans, the
317
- // host fetches full correlated events (withData=true) and materializes a wait
318
- // entity, so this includes resumeAt/completedAt without bloating trace payloads.
319
- const displayData = (spanDetailData ?? data) as
320
- | WorkflowRun
321
- | Step
322
- | Hook
323
- | Event;
328
+ const displayData = useMemo(
329
+ () =>
330
+ mergeSpanDetail(data, matchedSpanDetailData) as
331
+ | WorkflowRun
332
+ | Step
333
+ | Hook
334
+ | Event,
335
+ [data, matchedSpanDetailData]
336
+ );
324
337
 
325
338
  const moduleSpecifier = useMemo(() => {
326
339
  const displayRecord = displayData as Record<string, unknown>;
@@ -435,6 +448,8 @@ export function EntityDetailPanel({
435
448
  <EventsList
436
449
  events={rawEvents}
437
450
  onLoadEventData={onLoadEventData}
451
+ onStreamClick={onStreamClick}
452
+ onRunClick={onRunClick}
438
453
  encryptionKey={encryptionKey}
439
454
  />
440
455
  )}
@@ -3,6 +3,7 @@
3
3
  import { EVENT_DATA_REF_FIELDS, type Event } from '@workflow/world';
4
4
  import { useCallback, useLayoutEffect, useMemo, useRef, useState } from 'react';
5
5
  import { hasEncryptedFields, isExpiredMarker } from '../../lib/hydration';
6
+ import { RunClickContext, StreamClickContext } from '../ui/data-inspector';
6
7
  import { ErrorCard } from '../ui/error-card';
7
8
  import { ErrorStackBlock, isStructuredError } from '../ui/error-stack-block';
8
9
  import { Skeleton } from '../ui/skeleton';
@@ -256,6 +257,8 @@ export function EventsList({
256
257
  isLoading = false,
257
258
  error,
258
259
  onLoadEventData,
260
+ onStreamClick,
261
+ onRunClick,
259
262
  encryptionKey,
260
263
  }: {
261
264
  events: Event[];
@@ -265,6 +268,8 @@ export function EventsList({
265
268
  correlationId: string,
266
269
  eventId: string
267
270
  ) => Promise<unknown | null>;
271
+ onStreamClick?: (streamId: string) => void;
272
+ onRunClick?: (runId: string) => void;
268
273
  /** When provided, signals that decryption is active (triggers re-load of expanded events) */
269
274
  encryptionKey?: Uint8Array;
270
275
  }) {
@@ -285,31 +290,35 @@ export function EventsList({
285
290
  }
286
291
 
287
292
  return (
288
- <DetailCard summary="Events" contentClassName="mb-0" defaultOpen>
289
- {isLoading ? (
290
- <div className="flex flex-col -mx-4">
291
- {[0, 1, 2].map((i) => (
292
- <div
293
- key={i}
294
- className="flex items-center justify-between gap-3 bg-background-200 px-4 py-2"
295
- >
296
- <Skeleton className="h-4 w-32 rounded" />
297
- <Skeleton className="h-3 w-16 rounded" />
293
+ <RunClickContext.Provider value={onRunClick}>
294
+ <StreamClickContext.Provider value={onStreamClick}>
295
+ <DetailCard summary="Events" contentClassName="mb-0" defaultOpen>
296
+ {isLoading ? (
297
+ <div className="flex flex-col -mx-4">
298
+ {[0, 1, 2].map((i) => (
299
+ <div
300
+ key={i}
301
+ className="flex items-center justify-between gap-3 bg-background-200 px-4 py-2"
302
+ >
303
+ <Skeleton className="h-4 w-32 rounded" />
304
+ <Skeleton className="h-3 w-16 rounded" />
305
+ </div>
306
+ ))}
298
307
  </div>
299
- ))}
300
- </div>
301
- ) : (
302
- <div className="flex flex-col -mx-4">
303
- {sortedEvents.map((event) => (
304
- <EventItem
305
- key={event.eventId}
306
- event={event}
307
- onLoadEventData={onLoadEventData}
308
- encryptionKey={encryptionKey}
309
- />
310
- ))}
311
- </div>
312
- )}
313
- </DetailCard>
308
+ ) : (
309
+ <div className="flex flex-col -mx-4">
310
+ {sortedEvents.map((event) => (
311
+ <EventItem
312
+ key={event.eventId}
313
+ event={event}
314
+ onLoadEventData={onLoadEventData}
315
+ encryptionKey={encryptionKey}
316
+ />
317
+ ))}
318
+ </div>
319
+ )}
320
+ </DetailCard>
321
+ </StreamClickContext.Provider>
322
+ </RunClickContext.Provider>
314
323
  );
315
324
  }