executable-stories-formatters 0.1.0 → 0.3.0

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) Jag Reehal
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -119,7 +119,7 @@ const generator = new ReportGenerator({
119
119
 
120
120
  | Format | Description | File Extension |
121
121
  | --- | --- | --- |
122
- | `html` | Interactive HTML report with search and screenshots | `.html` |
122
+ | `html` | Interactive HTML report with search, screenshots, step parameter highlighting (quoted strings and numbers), syntax-highlighted code blocks, Mermaid diagrams, and Markdown in doc sections | `.html` |
123
123
  | `markdown` | Markdown user-story documentation | `.md` |
124
124
  | `junit` | JUnit XML for CI integration | `.junit.xml` |
125
125
  | `cucumber-json` | Cucumber JSON for tooling compatibility | `.cucumber.json` |
package/dist/cli.js CHANGED
@@ -896,50 +896,162 @@ function initTheme() {
896
896
  }
897
897
  `;
898
898
  var JS_CORE = `
899
+ // Filter state
900
+ var activeTags = new Set();
901
+ var activeStatus = null;
902
+
899
903
  // Search functionality
900
904
  function initSearch() {
901
- const input = document.querySelector('.search-input');
905
+ var input = document.querySelector('.search-input');
902
906
  if (!input) return;
903
907
 
904
- let debounceTimer;
905
- input.addEventListener('input', (e) => {
908
+ var debounceTimer;
909
+ input.addEventListener('input', function() {
906
910
  clearTimeout(debounceTimer);
907
- debounceTimer = setTimeout(() => {
908
- filterScenarios(e.target.value.toLowerCase().trim());
911
+ debounceTimer = setTimeout(function() {
912
+ applyAllFilters();
909
913
  }, 150);
910
914
  });
911
915
 
912
916
  // Clear search on Escape
913
- input.addEventListener('keydown', (e) => {
917
+ input.addEventListener('keydown', function(e) {
914
918
  if (e.key === 'Escape') {
915
919
  e.target.value = '';
916
- filterScenarios('');
920
+ applyAllFilters();
917
921
  }
918
922
  });
919
923
  }
920
924
 
921
- function filterScenarios(query) {
922
- const features = document.querySelectorAll('.feature');
925
+ // Tag filter
926
+ function initTagFilter() {
927
+ document.querySelectorAll('.tag-pill').forEach(function(pill) {
928
+ pill.addEventListener('click', function() {
929
+ var tag = pill.dataset.tag;
930
+ if (activeTags.has(tag)) {
931
+ activeTags.delete(tag);
932
+ pill.classList.remove('active');
933
+ } else {
934
+ activeTags.add(tag);
935
+ pill.classList.add('active');
936
+ }
937
+ updateClearButton();
938
+ applyAllFilters();
939
+ });
940
+ });
923
941
 
924
- features.forEach(feature => {
925
- const scenarios = feature.querySelectorAll('.scenario');
926
- let visibleCount = 0;
942
+ var clearBtn = document.querySelector('.tag-bar-clear');
943
+ if (clearBtn) {
944
+ clearBtn.addEventListener('click', function() {
945
+ activeTags.clear();
946
+ document.querySelectorAll('.tag-pill.active').forEach(function(p) { p.classList.remove('active'); });
947
+ updateClearButton();
948
+ applyAllFilters();
949
+ });
950
+ }
951
+ }
927
952
 
928
- scenarios.forEach(scenario => {
929
- const title = scenario.querySelector('.scenario-title')?.textContent?.toLowerCase() || '';
930
- const tags = Array.from(scenario.querySelectorAll('.tag')).map(t => t.textContent.toLowerCase());
931
- const steps = Array.from(scenario.querySelectorAll('.step-text')).map(s => s.textContent.toLowerCase());
953
+ function updateClearButton() {
954
+ var clearBtn = document.querySelector('.tag-bar-clear');
955
+ if (clearBtn) {
956
+ clearBtn.style.display = activeTags.size > 0 ? '' : 'none';
957
+ }
958
+ }
932
959
 
933
- const matches = !query ||
934
- title.includes(query) ||
935
- tags.some(t => t.includes(query)) ||
936
- steps.some(s => s.includes(query));
960
+ // Status filter (clickable summary cards)
961
+ function initStatusFilter() {
962
+ document.querySelectorAll('.summary-card').forEach(function(card) {
963
+ card.style.cursor = 'pointer';
964
+ if (!card.classList.contains('passed') && !card.classList.contains('failed') && !card.classList.contains('skipped')) {
965
+ card.addEventListener('click', function() {
966
+ activeStatus = null;
967
+ document.querySelectorAll('.summary-card').forEach(function(c) { c.classList.remove('status-active'); });
968
+ applyAllFilters();
969
+ });
970
+ return;
971
+ }
972
+ card.addEventListener('click', function() {
973
+ var status = card.classList.contains('passed') ? 'passed' :
974
+ card.classList.contains('failed') ? 'failed' : 'skipped';
975
+ if (activeStatus === status) {
976
+ activeStatus = null;
977
+ card.classList.remove('status-active');
978
+ } else {
979
+ activeStatus = status;
980
+ document.querySelectorAll('.summary-card').forEach(function(c) { c.classList.remove('status-active'); });
981
+ card.classList.add('status-active');
982
+ }
983
+ applyAllFilters();
984
+ });
985
+ });
986
+ }
937
987
 
938
- scenario.style.display = matches ? '' : 'none';
939
- if (matches) visibleCount++;
988
+ // Unified filter: composes search + tags + status
989
+ function applyAllFilters() {
990
+ var searchInput = document.querySelector('.search-input');
991
+ var searchQuery = searchInput ? searchInput.value.toLowerCase().trim() : '';
992
+ var features = document.querySelectorAll('.feature');
993
+ var visibleCount = 0;
994
+ var totalCount = 0;
995
+
996
+ features.forEach(function(feature) {
997
+ var scenarios = feature.querySelectorAll('.scenario');
998
+ var featureVisible = 0;
999
+
1000
+ scenarios.forEach(function(scenario) {
1001
+ totalCount++;
1002
+ var title = (scenario.querySelector('.scenario-title') || {}).textContent || '';
1003
+ title = title.toLowerCase();
1004
+ var tags = Array.from(scenario.querySelectorAll('.scenario-meta .tag')).map(function(t) { return t.textContent.toLowerCase(); });
1005
+ var steps = Array.from(scenario.querySelectorAll('.step-text')).map(function(s) { return s.textContent.toLowerCase(); });
1006
+ var statusEl = scenario.querySelector('.status-icon');
1007
+ var status = statusEl && statusEl.classList.contains('status-passed') ? 'passed' :
1008
+ statusEl && statusEl.classList.contains('status-failed') ? 'failed' :
1009
+ statusEl && statusEl.classList.contains('status-skipped') ? 'skipped' : 'pending';
1010
+
1011
+ var matchesSearch = !searchQuery ||
1012
+ title.includes(searchQuery) ||
1013
+ tags.some(function(t) { return t.includes(searchQuery); }) ||
1014
+ steps.some(function(s) { return s.includes(searchQuery); });
1015
+
1016
+ var matchesTags = activeTags.size === 0 ||
1017
+ tags.some(function(t) { return activeTags.has(t); });
1018
+
1019
+ var matchesStatus = !activeStatus ||
1020
+ status === activeStatus ||
1021
+ (activeStatus === 'skipped' && status === 'pending');
1022
+
1023
+ var visible = matchesSearch && matchesTags && matchesStatus;
1024
+ scenario.style.display = visible ? '' : 'none';
1025
+ if (visible) { visibleCount++; featureVisible++; }
940
1026
  });
941
1027
 
942
- feature.style.display = visibleCount > 0 ? '' : 'none';
1028
+ feature.style.display = featureVisible > 0 ? '' : 'none';
1029
+ });
1030
+
1031
+ updateFilterResults(visibleCount, totalCount);
1032
+ }
1033
+
1034
+ function updateFilterResults(visible, total) {
1035
+ var el = document.querySelector('.filter-results');
1036
+ if (!el) return;
1037
+ var searchInput = document.querySelector('.search-input');
1038
+ var isFiltering = activeTags.size > 0 || activeStatus ||
1039
+ (searchInput && searchInput.value.trim().length > 0);
1040
+ el.style.display = isFiltering ? '' : 'none';
1041
+ var vc = el.querySelector('.visible-count');
1042
+ var tc = el.querySelector('.total-count');
1043
+ if (vc) vc.textContent = visible;
1044
+ if (tc) tc.textContent = total;
1045
+ }
1046
+
1047
+ // Keyboard shortcuts
1048
+ function initKeyboardShortcuts() {
1049
+ document.addEventListener('keydown', function(e) {
1050
+ if (e.key === '/' && !e.ctrlKey && !e.metaKey && e.target.tagName !== 'INPUT') {
1051
+ e.preventDefault();
1052
+ var input = document.querySelector('.search-input');
1053
+ if (input) input.focus();
1054
+ }
943
1055
  });
944
1056
  }
945
1057
 
@@ -974,6 +1086,18 @@ function initCollapse() {
974
1086
  }
975
1087
  });
976
1088
  });
1089
+
1090
+ document.querySelectorAll('.trace-view-header').forEach(header => {
1091
+ header.addEventListener('click', () => {
1092
+ toggleCollapse(header, header.closest('.trace-view'));
1093
+ });
1094
+ header.addEventListener('keydown', (e) => {
1095
+ if (e.key === 'Enter' || e.key === ' ') {
1096
+ e.preventDefault();
1097
+ toggleCollapse(header, header.closest('.trace-view'));
1098
+ }
1099
+ });
1100
+ });
977
1101
  }
978
1102
 
979
1103
  function expandAll() {
@@ -1024,6 +1148,9 @@ function generateScript(options) {
1024
1148
  initCalls.push("initTheme();");
1025
1149
  }
1026
1150
  initCalls.push("initSearch();");
1151
+ initCalls.push("initTagFilter();");
1152
+ initCalls.push("initStatusFilter();");
1153
+ initCalls.push("initKeyboardShortcuts();");
1027
1154
  initCalls.push("initCollapse();");
1028
1155
  const initScript = `
1029
1156
  // Initialize on load
@@ -1174,6 +1301,7 @@ var CSS_STYLES = `
1174
1301
  --tag-bg: hsl(145 55% 95%);
1175
1302
  --tag-color: hsl(145 63% 30%);
1176
1303
  --tag-border: hsl(145 55% 85%);
1304
+ --step-param-color: hsl(220 70% 50%);
1177
1305
 
1178
1306
  /* Accordion/Collapsible styling */
1179
1307
  --accordion-header-hover: hsl(0 0% 98%);
@@ -1232,6 +1360,7 @@ var CSS_STYLES = `
1232
1360
  --tag-bg: hsl(145 35% 14%);
1233
1361
  --tag-color: hsl(145 63% 60%);
1234
1362
  --tag-border: hsl(145 35% 22%);
1363
+ --step-param-color: hsl(220 70% 70%);
1235
1364
 
1236
1365
  /* Accordion/Collapsible styling */
1237
1366
  --accordion-header-hover: hsl(0 0% 11%);
@@ -1280,6 +1409,7 @@ var CSS_STYLES = `
1280
1409
  --tag-bg: hsl(145 35% 14%);
1281
1410
  --tag-color: hsl(145 63% 60%);
1282
1411
  --tag-border: hsl(145 35% 22%);
1412
+ --step-param-color: hsl(220 70% 70%);
1283
1413
  --accordion-header-hover: hsl(0 0% 11%);
1284
1414
  --accordion-content-bg: hsl(0 0% 7%);
1285
1415
  }
@@ -1507,6 +1637,98 @@ body {
1507
1637
  }
1508
1638
  .summary-card.pending .value { color: var(--pending); }
1509
1639
 
1640
+ /* ============================================================================
1641
+ Tag Filter Bar
1642
+ ============================================================================ */
1643
+ .tag-bar {
1644
+ margin-bottom: 1rem;
1645
+ padding: 0.75rem 1rem;
1646
+ background: var(--card);
1647
+ border: 1px solid var(--border);
1648
+ border-radius: var(--radius);
1649
+ position: sticky;
1650
+ top: 0;
1651
+ z-index: 10;
1652
+ }
1653
+
1654
+ .tag-bar-header {
1655
+ display: flex;
1656
+ justify-content: space-between;
1657
+ align-items: center;
1658
+ margin-bottom: 0.5rem;
1659
+ }
1660
+
1661
+ .tag-bar-label {
1662
+ font-size: 0.6875rem;
1663
+ text-transform: uppercase;
1664
+ letter-spacing: 0.05em;
1665
+ color: var(--muted-foreground);
1666
+ font-weight: 500;
1667
+ }
1668
+
1669
+ .tag-bar-clear {
1670
+ font-size: 0.75rem;
1671
+ font-weight: 500;
1672
+ color: var(--primary);
1673
+ background: none;
1674
+ border: none;
1675
+ cursor: pointer;
1676
+ padding: 0.125rem 0.5rem;
1677
+ border-radius: var(--radius);
1678
+ transition: all 0.15s ease;
1679
+ }
1680
+
1681
+ .tag-bar-clear:hover {
1682
+ background: var(--muted);
1683
+ }
1684
+
1685
+ .tag-bar-pills {
1686
+ display: flex;
1687
+ flex-wrap: wrap;
1688
+ gap: 0.375rem;
1689
+ }
1690
+
1691
+ .tag-pill {
1692
+ font-size: 0.75rem;
1693
+ font-weight: 500;
1694
+ padding: 0.25rem 0.625rem;
1695
+ background: var(--tag-bg);
1696
+ color: var(--tag-color);
1697
+ border: 1px solid var(--tag-border);
1698
+ border-radius: 9999px;
1699
+ font-family: var(--font-mono);
1700
+ cursor: pointer;
1701
+ transition: all 0.15s ease;
1702
+ }
1703
+
1704
+ .tag-pill:hover {
1705
+ background: var(--success-border);
1706
+ }
1707
+
1708
+ .tag-pill.active {
1709
+ background: var(--primary);
1710
+ color: var(--primary-foreground);
1711
+ border-color: var(--primary);
1712
+ }
1713
+
1714
+ /* ============================================================================
1715
+ Summary Card Status Filter
1716
+ ============================================================================ */
1717
+ .summary-card.status-active {
1718
+ box-shadow: 0 0 0 2px var(--background), 0 0 0 4px var(--ring);
1719
+ }
1720
+
1721
+ /* ============================================================================
1722
+ Filter Results Counter
1723
+ ============================================================================ */
1724
+ .filter-results {
1725
+ text-align: center;
1726
+ font-size: 0.8125rem;
1727
+ color: var(--muted-foreground);
1728
+ margin-bottom: 1rem;
1729
+ font-weight: 500;
1730
+ }
1731
+
1510
1732
  /* ============================================================================
1511
1733
  Feature Sections - shadcn accordion style
1512
1734
  ============================================================================ */
@@ -1733,6 +1955,12 @@ body {
1733
1955
  color: var(--foreground);
1734
1956
  }
1735
1957
 
1958
+ .step-param {
1959
+ font-style: italic;
1960
+ font-weight: 500;
1961
+ color: var(--step-param-color);
1962
+ }
1963
+
1736
1964
  .step-duration {
1737
1965
  color: var(--muted-foreground);
1738
1966
  font-size: 0.6875rem;
@@ -1894,8 +2122,10 @@ body {
1894
2122
  padding: 0;
1895
2123
  }
1896
2124
 
1897
- .header-actions {
1898
- display: none;
2125
+ .header-actions,
2126
+ .tag-bar,
2127
+ .filter-results {
2128
+ display: none !important;
1899
2129
  }
1900
2130
 
1901
2131
  .feature,
@@ -2407,6 +2637,7 @@ body {
2407
2637
  font-family: inherit;
2408
2638
  background: none;
2409
2639
  }
2640
+
2410
2641
  `;
2411
2642
 
2412
2643
  // src/formatters/html/renderers/status.ts
@@ -2469,6 +2700,28 @@ function renderSummary(args, _deps) {
2469
2700
  </div>`;
2470
2701
  }
2471
2702
 
2703
+ // src/formatters/html/renderers/tag-bar.ts
2704
+ function renderTagBar(args, deps) {
2705
+ const { tags, totalScenarios } = args;
2706
+ if (tags.length === 0) return "";
2707
+ const pills = tags.map(
2708
+ (tag) => `<button type="button" class="tag-pill" data-tag="${deps.escapeHtml(tag)}">${deps.escapeHtml(tag)}</button>`
2709
+ ).join("\n ");
2710
+ return `
2711
+ <div class="tag-bar">
2712
+ <div class="tag-bar-header">
2713
+ <span class="tag-bar-label">Filter by tag</span>
2714
+ <button type="button" class="tag-bar-clear" style="display:none">Clear</button>
2715
+ </div>
2716
+ <div class="tag-bar-pills">
2717
+ ${pills}
2718
+ </div>
2719
+ </div>
2720
+ <div class="filter-results" style="display:none">
2721
+ Showing <span class="visible-count">0</span> of <span class="total-count">${totalScenarios}</span> scenarios
2722
+ </div>`;
2723
+ }
2724
+
2472
2725
  // src/formatters/html/renderers/error-box.ts
2473
2726
  function renderErrorBox(args, deps) {
2474
2727
  const body = args.stack != null ? `${deps.escapeHtml(args.message)}
@@ -2629,10 +2882,11 @@ function renderStep(step, stepResult, index, deps) {
2629
2882
  const isContinuation = CONTINUATION_KEYWORDS.includes(keywordTrimmed);
2630
2883
  const stepClass = isContinuation ? "step continuation" : "step";
2631
2884
  const stepDocs = deps.renderDocs(step.docs, "step-docs");
2885
+ const textHtml = deps.highlightStepParams ? deps.highlightStepParams(step.text) : deps.escapeHtml(step.text);
2632
2886
  return `<div class="${stepClass}">
2633
2887
  <span class="step-status ${statusClass}">${statusIcon}</span>
2634
2888
  <span class="step-keyword">${deps.escapeHtml(step.keyword)}</span>
2635
- <span class="step-text">${deps.escapeHtml(step.text)}</span>
2889
+ <span class="step-text">${textHtml}</span>
2636
2890
  <span class="step-duration">${duration}</span>
2637
2891
  </div>${stepDocs}`;
2638
2892
  }
@@ -2644,6 +2898,30 @@ function renderSteps(args, deps) {
2644
2898
  return `<div class="steps">${stepsHtml}</div>`;
2645
2899
  }
2646
2900
 
2901
+ // src/formatters/html/renderers/step-params.ts
2902
+ var STEP_PARAM_PATTERN = /"[^"]*"|(?<![\w.\-])\d+(?:\.\d+)?(?![\w.\-])/g;
2903
+ function highlightStepParams(text, deps) {
2904
+ const matches = Array.from(text.matchAll(STEP_PARAM_PATTERN));
2905
+ if (matches.length === 0) {
2906
+ return deps.escapeHtml(text);
2907
+ }
2908
+ let result = "";
2909
+ let lastIndex = 0;
2910
+ for (const match of matches) {
2911
+ const matchStart = match.index;
2912
+ const matchEnd = matchStart + match[0].length;
2913
+ if (matchStart > lastIndex) {
2914
+ result += deps.escapeHtml(text.slice(lastIndex, matchStart));
2915
+ }
2916
+ result += `<span class="step-param">${deps.escapeHtml(match[0])}</span>`;
2917
+ lastIndex = matchEnd;
2918
+ }
2919
+ if (lastIndex < text.length) {
2920
+ result += deps.escapeHtml(text.slice(lastIndex));
2921
+ }
2922
+ return result;
2923
+ }
2924
+
2647
2925
  // src/formatters/html/renderers/scenario.ts
2648
2926
  function renderScenario(args, deps) {
2649
2927
  const { tc } = args;
@@ -2651,6 +2929,19 @@ function renderScenario(args, deps) {
2651
2929
  const statusClass = `status-${tc.status}`;
2652
2930
  const duration = tc.durationMs > 0 ? `${(tc.durationMs / 1e3).toFixed(2)}s` : "";
2653
2931
  const tags = tc.tags.map((t) => `<span class="tag">${deps.escapeHtml(t)}</span>`).join("");
2932
+ const otelMeta = tc.story.meta?.otel;
2933
+ let traceBadge = "";
2934
+ if (otelMeta?.traceId) {
2935
+ const shortId = otelMeta.traceId.slice(0, 16);
2936
+ const traceLink = tc.story.docs?.find(
2937
+ (d) => d.kind === "link" && d.label === "View Trace"
2938
+ );
2939
+ if (traceLink) {
2940
+ traceBadge = `<a class="tag trace-tag" href="${deps.escapeHtml(traceLink.url)}" title="${deps.escapeHtml(otelMeta.traceId)}" target="_blank" rel="noopener">${deps.escapeHtml(shortId)}\u2026</a>`;
2941
+ } else {
2942
+ traceBadge = `<span class="tag trace-tag" title="${deps.escapeHtml(otelMeta.traceId)}">${deps.escapeHtml(shortId)}\u2026</span>`;
2943
+ }
2944
+ }
2654
2945
  const storyDocs = deps.renderDocs(tc.story.docs, "story-docs");
2655
2946
  const steps = deps.renderSteps(
2656
2947
  { steps: tc.story.steps, stepResults: tc.stepResults },
@@ -2681,7 +2972,7 @@ function renderScenario(args, deps) {
2681
2972
  <span class="status-icon ${statusClass}">${statusIcon}</span>
2682
2973
  <span class="scenario-name">${deps.escapeHtml(tc.story.scenario)}</span>
2683
2974
  </div>
2684
- <div class="scenario-meta">${tags}</div>
2975
+ <div class="scenario-meta">${tags}${traceBadge}</div>
2685
2976
  </div>
2686
2977
  <span class="scenario-duration">${duration}</span>
2687
2978
  </div>
@@ -2768,6 +3059,15 @@ function buildBody(args, deps) {
2768
3059
  deps.summaryDeps
2769
3060
  )
2770
3061
  );
3062
+ const allTags = [
3063
+ ...new Set(run.testCases.flatMap((tc) => tc.tags))
3064
+ ].sort();
3065
+ parts.push(
3066
+ deps.renderTagBar(
3067
+ { tags: allTags, totalScenarios: total },
3068
+ deps.tagBarDeps
3069
+ )
3070
+ );
2771
3071
  const byFile = groupBy(run.testCases, (tc) => tc.sourceFile);
2772
3072
  for (const [file, testCases] of byFile) {
2773
3073
  parts.push(
@@ -2806,7 +3106,8 @@ function createHtmlFormatter(options = {}) {
2806
3106
  const stepsDeps = {
2807
3107
  escapeHtml,
2808
3108
  getStatusIcon,
2809
- renderDocs
3109
+ renderDocs,
3110
+ highlightStepParams: (text) => highlightStepParams(text, { escapeHtml })
2810
3111
  };
2811
3112
  const scenarioDeps = {
2812
3113
  escapeHtml,
@@ -2824,12 +3125,15 @@ function createHtmlFormatter(options = {}) {
2824
3125
  renderScenario: (args) => renderScenario(args, scenarioDeps),
2825
3126
  scenarioDeps
2826
3127
  };
3128
+ const tagBarDeps = { escapeHtml };
2827
3129
  const bodyDeps = {
2828
3130
  renderMetaInfo,
2829
3131
  renderSummary,
3132
+ renderTagBar,
2830
3133
  renderFeature,
2831
3134
  metaDeps: { escapeHtml },
2832
3135
  summaryDeps: {},
3136
+ tagBarDeps,
2833
3137
  featureDeps
2834
3138
  };
2835
3139
  return {
@@ -3105,6 +3409,7 @@ var MarkdownFormatter = class {
3105
3409
  includeSummaryTable: options.includeSummaryTable ?? false,
3106
3410
  permalinkBaseUrl: options.permalinkBaseUrl,
3107
3411
  ticketUrlTemplate: options.ticketUrlTemplate,
3412
+ traceUrlTemplate: options.traceUrlTemplate,
3108
3413
  includeSourceLinks: options.includeSourceLinks ?? true,
3109
3414
  customRenderers: options.customRenderers
3110
3415
  };
@@ -3327,6 +3632,18 @@ var MarkdownFormatter = class {
3327
3632
  meta.push(`Tickets: ${tc.story.tickets.map((t) => `\`${t}\``).join(", ")}`);
3328
3633
  }
3329
3634
  }
3635
+ const otelMeta = tc.story.meta?.otel;
3636
+ if (otelMeta?.traceId) {
3637
+ const traceTemplate = this.options.traceUrlTemplate;
3638
+ if (traceTemplate) {
3639
+ const url = traceTemplate.replace(/\{traceId\}/g, otelMeta.traceId);
3640
+ meta.push(
3641
+ `Trace: [${otelMeta.traceId.slice(0, 16)}\u2026](${url})`
3642
+ );
3643
+ } else {
3644
+ meta.push(`Trace: \`${otelMeta.traceId}\``);
3645
+ }
3646
+ }
3330
3647
  if (meta.length > 0) {
3331
3648
  lines.push(meta.join(" | "));
3332
3649
  }
@@ -4881,6 +5198,7 @@ var ReportGenerator = class {
4881
5198
  includeSummaryTable: options.markdown?.includeSummaryTable ?? false,
4882
5199
  permalinkBaseUrl: options.markdown?.permalinkBaseUrl,
4883
5200
  ticketUrlTemplate: options.markdown?.ticketUrlTemplate,
5201
+ traceUrlTemplate: options.markdown?.traceUrlTemplate,
4884
5202
  includeSourceLinks: options.markdown?.includeSourceLinks ?? true,
4885
5203
  customRenderers: options.markdown?.customRenderers
4886
5204
  }
@@ -5006,6 +5324,7 @@ var ReportGenerator = class {
5006
5324
  includeSummaryTable: this.options.markdown.includeSummaryTable,
5007
5325
  permalinkBaseUrl: this.options.markdown.permalinkBaseUrl,
5008
5326
  ticketUrlTemplate: this.options.markdown.ticketUrlTemplate,
5327
+ traceUrlTemplate: this.options.markdown.traceUrlTemplate,
5009
5328
  includeSourceLinks: this.options.markdown.includeSourceLinks,
5010
5329
  customRenderers: this.options.markdown.customRenderers
5011
5330
  });