@reportforge/playwright-pdf 0.21.0 → 0.22.1

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/dist/index.js CHANGED
@@ -10330,7 +10330,10 @@ var DEFAULT_OPTIONS = {
10330
10330
  historySize: 10,
10331
10331
  showTrend: true,
10332
10332
  flakinessTopN: 5,
10333
- slowTestThreshold: 10
10333
+ slowTestThreshold: 10,
10334
+ // Ticket-ID shape: @ODP-5328, REQ-001, JIRA-123. Tags matching this land in
10335
+ // the Requirements Traceability matrix; the rest fall to the tag summary.
10336
+ requirementTagPattern: "^@?[A-Z][A-Z0-9]*-\\d+$"
10334
10337
  };
10335
10338
 
10336
10339
  // src/templates/sections.ts
@@ -10559,6 +10562,14 @@ var ReporterOptionsSchema = external_exports.object({
10559
10562
  remoteHistory: external_exports.boolean().default(false),
10560
10563
  flakinessTopN: external_exports.number().int().min(0, "flakinessTopN must be 0 or greater").optional().default(DEFAULT_OPTIONS.flakinessTopN),
10561
10564
  slowTestThreshold: external_exports.number().int().min(0, "slowTestThreshold must be 0 or greater").optional().default(DEFAULT_OPTIONS.slowTestThreshold),
10565
+ requirementTagPattern: external_exports.string().refine((v) => {
10566
+ try {
10567
+ new RegExp(v);
10568
+ return true;
10569
+ } catch {
10570
+ return false;
10571
+ }
10572
+ }, "requirementTagPattern must be a valid regular expression").optional().default(DEFAULT_OPTIONS.requirementTagPattern),
10562
10573
  failureAnalysis: failureAnalysisConfigSchema,
10563
10574
  capture: captureConfigSchema,
10564
10575
  live: liveConfigSchema,
@@ -12638,6 +12649,7 @@ var PAGE_CONTENT_HEIGHT_PX = (A4_HEIGHT_MM - 2 * PAGE_MARGIN_Y_MM) * PX_PER_MM;
12638
12649
  function isUnknownValue(v) {
12639
12650
  return !v || v === "unknown";
12640
12651
  }
12652
+ var NUMERIC_COLLATOR = new Intl.Collator(void 0, { numeric: true });
12641
12653
  var SLOW_MEDIAN_FACTOR = 2;
12642
12654
  function buildSuiteChartChunks(rows) {
12643
12655
  const chunks = [];
@@ -12687,17 +12699,22 @@ var TemplateEngine = class {
12687
12699
  setChartjsBundle(inline) {
12688
12700
  this.chartjsInline = inline;
12689
12701
  }
12690
- render(data, template, userSections, slowTestThreshold) {
12702
+ render(data, template, opts = {}) {
12691
12703
  const layoutPath = import_path3.default.join(this.templatesDir, "layouts", `${template}.hbs`);
12692
12704
  if (!import_fs4.default.existsSync(layoutPath)) {
12693
12705
  throw new Error(`[reportforge] Template layout not found: ${layoutPath}`);
12694
12706
  }
12695
12707
  const layoutSource = import_fs4.default.readFileSync(layoutPath, "utf8");
12696
12708
  const compiledLayout = this.handlebars.compile(layoutSource);
12697
- const ctx = this.buildContext(data, template, userSections, slowTestThreshold);
12709
+ const ctx = this.assembleContext(
12710
+ data,
12711
+ resolveSections(template, opts.sections),
12712
+ this.readStyle(`${template}.css`),
12713
+ opts
12714
+ );
12698
12715
  return compiledLayout(ctx);
12699
12716
  }
12700
- renderFromPath(data, filePath, slowTestThreshold) {
12717
+ renderFromPath(data, filePath, opts = {}) {
12701
12718
  if (!this.customTemplateCache.has(filePath)) {
12702
12719
  if (!import_fs4.default.existsSync(filePath)) {
12703
12720
  throw new Error(`[reportforge] Custom template not found: ${filePath}`);
@@ -12706,46 +12723,27 @@ var TemplateEngine = class {
12706
12723
  this.customTemplateCache.set(filePath, this.handlebars.compile(source));
12707
12724
  }
12708
12725
  const compiledFn = this.customTemplateCache.get(filePath);
12709
- return compiledFn(this.buildCustomContext(data, slowTestThreshold));
12726
+ return compiledFn(this.assembleContext(data, allSectionsOn(), "", opts));
12710
12727
  }
12711
- buildCustomContext(data, slowTestThreshold) {
12712
- const sections = allSectionsOn();
12713
- const slow = this.buildSlowTests(data);
12714
- return {
12715
- ...data,
12716
- projects: this.enrichProjects(data, slow, sections, slowTestThreshold ?? DEFAULT_OPTIONS.slowTestThreshold),
12717
- options: deriveOptions(sections),
12718
- sections,
12719
- tagGroups: this.buildTagGroups(data),
12720
- slowTests: slow.ranked,
12721
- baseCSS: this.fontsBundleCss + "\n" + this.readStyle("base.css"),
12722
- templateCSS: "",
12723
- chartjsScript: this.buildChartjsScript(),
12724
- watermarkPageHeightPx: PAGE_CONTENT_HEIGHT_PX,
12725
- releaseGateTally: buildFailureTally(data.analysis),
12726
- suiteChartLarge: data.charts.suiteResults.length > SUITE_CHART_SMALL_MAX,
12727
- suiteChartChunks: data.charts.suiteResults.length > SUITE_CHART_SMALL_MAX ? buildSuiteChartChunks(data.charts.suiteResults) : []
12728
- };
12729
- }
12730
- buildContext(data, template, userSections, slowTestThreshold) {
12731
- const baseCSS = this.fontsBundleCss + "\n" + this.readStyle("base.css");
12732
- const templateCSS = this.readStyle(`${template}.css`);
12733
- const sections = resolveSections(template, userSections);
12728
+ assembleContext(data, sections, templateCSS, opts) {
12734
12729
  const options = deriveOptions(sections);
12735
12730
  const slow = this.buildSlowTests(data);
12731
+ const tags = this.buildTagGroups(data, opts.requirementTagPattern);
12736
12732
  return {
12737
12733
  ...data,
12738
12734
  projects: this.enrichProjects(
12739
12735
  data,
12740
12736
  slow,
12741
12737
  sections,
12742
- slowTestThreshold ?? DEFAULT_OPTIONS.slowTestThreshold
12738
+ opts.slowTestThreshold ?? DEFAULT_OPTIONS.slowTestThreshold
12743
12739
  ),
12744
12740
  options,
12745
12741
  sections,
12746
- tagGroups: this.buildTagGroups(data),
12742
+ tagGroups: tags.all,
12743
+ requirementTagGroups: tags.requirements,
12744
+ categoryTagGroups: tags.categories,
12747
12745
  slowTests: slow.ranked,
12748
- baseCSS,
12746
+ baseCSS: this.fontsBundleCss + "\n" + this.readStyle("base.css"),
12749
12747
  templateCSS,
12750
12748
  chartjsScript: this.buildChartjsScript(),
12751
12749
  watermarkPageHeightPx: PAGE_CONTENT_HEIGHT_PX,
@@ -12757,7 +12755,7 @@ var TemplateEngine = class {
12757
12755
  buildChartjsScript() {
12758
12756
  return this.chartjsInline ? `<script>${this.chartjsInline}</script>` : "<!-- Chart.js not bundled; charts disabled --><script>window.__chartsReady=true;</script>";
12759
12757
  }
12760
- buildTagGroups(data) {
12758
+ buildTagGroups(data, requirementTagPattern) {
12761
12759
  const tagMap = /* @__PURE__ */ new Map();
12762
12760
  for (const project of data.projects) {
12763
12761
  for (const suite of this.flattenSuites(project.suites)) {
@@ -12772,11 +12770,20 @@ var TemplateEngine = class {
12772
12770
  }
12773
12771
  }
12774
12772
  }
12775
- return Array.from(tagMap.entries()).map(([tag, counts]) => ({
12776
- tag,
12777
- ...counts,
12778
- coveragePct: counts.total > 0 ? Math.round(counts.passed / counts.total * 100) : 0
12779
- }));
12773
+ const regex = new RegExp(requirementTagPattern ?? DEFAULT_OPTIONS.requirementTagPattern);
12774
+ const requirements = [];
12775
+ const categories = [];
12776
+ for (const [tag, counts] of tagMap) {
12777
+ const row = {
12778
+ tag,
12779
+ ...counts,
12780
+ coveragePct: counts.total > 0 ? Math.round(counts.passed / counts.total * 100) : 0
12781
+ };
12782
+ (regex.test(tag) ? requirements : categories).push(row);
12783
+ }
12784
+ requirements.sort((a, b) => NUMERIC_COLLATOR.compare(a.tag, b.tag));
12785
+ categories.sort((a, b) => b.total - a.total || a.tag.localeCompare(b.tag));
12786
+ return { all: [...requirements, ...categories], requirements, categories };
12780
12787
  }
12781
12788
  buildSlowTests(data) {
12782
12789
  const tests = [];
@@ -13533,7 +13540,12 @@ var PdfGenerator = class {
13533
13540
  }
13534
13541
  const templateLabel = source.kind === "builtin" ? source.id : source.label;
13535
13542
  logger.info(`Rendering template: ${templateLabel}`);
13536
- const html = source.kind === "builtin" ? this.templateEngine.render(renderData, source.id, options.sections, options.slowTestThreshold) : this.templateEngine.renderFromPath(renderData, source.absolutePath, options.slowTestThreshold);
13543
+ const renderOpts = {
13544
+ sections: options.sections,
13545
+ slowTestThreshold: options.slowTestThreshold,
13546
+ requirementTagPattern: options.requirementTagPattern
13547
+ };
13548
+ const html = source.kind === "builtin" ? this.templateEngine.render(renderData, source.id, renderOpts) : this.templateEngine.renderFromPath(renderData, source.absolutePath, renderOpts);
13537
13549
  const tempHtmlPath = await this.htmlWriter.write(html);
13538
13550
  const fileUrl = this.htmlWriter.toFileUrl(tempHtmlPath);
13539
13551
  try {
@@ -14401,7 +14413,7 @@ var PdfReporter = class {
14401
14413
  this.options = parseOptions(rawOptions);
14402
14414
  setLogLevel(this.options.logLevel);
14403
14415
  this.dataCollector = new DataCollector(this.options.capture);
14404
- this.version = true ? "0.21.0" : "0.x";
14416
+ this.version = true ? "0.22.1" : "0.x";
14405
14417
  logger.info(`@reportforge/playwright-pdf v${this.version} initialised`);
14406
14418
  this.licenseClient = new LicenseClient({
14407
14419
  licenseKey: this.options.licenseKey,
package/dist/index.mjs CHANGED
@@ -10331,7 +10331,10 @@ var DEFAULT_OPTIONS = {
10331
10331
  historySize: 10,
10332
10332
  showTrend: true,
10333
10333
  flakinessTopN: 5,
10334
- slowTestThreshold: 10
10334
+ slowTestThreshold: 10,
10335
+ // Ticket-ID shape: @ODP-5328, REQ-001, JIRA-123. Tags matching this land in
10336
+ // the Requirements Traceability matrix; the rest fall to the tag summary.
10337
+ requirementTagPattern: "^@?[A-Z][A-Z0-9]*-\\d+$"
10335
10338
  };
10336
10339
 
10337
10340
  // src/templates/sections.ts
@@ -10560,6 +10563,14 @@ var ReporterOptionsSchema = external_exports.object({
10560
10563
  remoteHistory: external_exports.boolean().default(false),
10561
10564
  flakinessTopN: external_exports.number().int().min(0, "flakinessTopN must be 0 or greater").optional().default(DEFAULT_OPTIONS.flakinessTopN),
10562
10565
  slowTestThreshold: external_exports.number().int().min(0, "slowTestThreshold must be 0 or greater").optional().default(DEFAULT_OPTIONS.slowTestThreshold),
10566
+ requirementTagPattern: external_exports.string().refine((v) => {
10567
+ try {
10568
+ new RegExp(v);
10569
+ return true;
10570
+ } catch {
10571
+ return false;
10572
+ }
10573
+ }, "requirementTagPattern must be a valid regular expression").optional().default(DEFAULT_OPTIONS.requirementTagPattern),
10563
10574
  failureAnalysis: failureAnalysisConfigSchema,
10564
10575
  capture: captureConfigSchema,
10565
10576
  live: liveConfigSchema,
@@ -12639,6 +12650,7 @@ var PAGE_CONTENT_HEIGHT_PX = (A4_HEIGHT_MM - 2 * PAGE_MARGIN_Y_MM) * PX_PER_MM;
12639
12650
  function isUnknownValue(v) {
12640
12651
  return !v || v === "unknown";
12641
12652
  }
12653
+ var NUMERIC_COLLATOR = new Intl.Collator(void 0, { numeric: true });
12642
12654
  var SLOW_MEDIAN_FACTOR = 2;
12643
12655
  function buildSuiteChartChunks(rows) {
12644
12656
  const chunks = [];
@@ -12688,17 +12700,22 @@ var TemplateEngine = class {
12688
12700
  setChartjsBundle(inline) {
12689
12701
  this.chartjsInline = inline;
12690
12702
  }
12691
- render(data, template, userSections, slowTestThreshold) {
12703
+ render(data, template, opts = {}) {
12692
12704
  const layoutPath = path9.join(this.templatesDir, "layouts", `${template}.hbs`);
12693
12705
  if (!fs7.existsSync(layoutPath)) {
12694
12706
  throw new Error(`[reportforge] Template layout not found: ${layoutPath}`);
12695
12707
  }
12696
12708
  const layoutSource = fs7.readFileSync(layoutPath, "utf8");
12697
12709
  const compiledLayout = this.handlebars.compile(layoutSource);
12698
- const ctx = this.buildContext(data, template, userSections, slowTestThreshold);
12710
+ const ctx = this.assembleContext(
12711
+ data,
12712
+ resolveSections(template, opts.sections),
12713
+ this.readStyle(`${template}.css`),
12714
+ opts
12715
+ );
12699
12716
  return compiledLayout(ctx);
12700
12717
  }
12701
- renderFromPath(data, filePath, slowTestThreshold) {
12718
+ renderFromPath(data, filePath, opts = {}) {
12702
12719
  if (!this.customTemplateCache.has(filePath)) {
12703
12720
  if (!fs7.existsSync(filePath)) {
12704
12721
  throw new Error(`[reportforge] Custom template not found: ${filePath}`);
@@ -12707,46 +12724,27 @@ var TemplateEngine = class {
12707
12724
  this.customTemplateCache.set(filePath, this.handlebars.compile(source));
12708
12725
  }
12709
12726
  const compiledFn = this.customTemplateCache.get(filePath);
12710
- return compiledFn(this.buildCustomContext(data, slowTestThreshold));
12727
+ return compiledFn(this.assembleContext(data, allSectionsOn(), "", opts));
12711
12728
  }
12712
- buildCustomContext(data, slowTestThreshold) {
12713
- const sections = allSectionsOn();
12714
- const slow = this.buildSlowTests(data);
12715
- return {
12716
- ...data,
12717
- projects: this.enrichProjects(data, slow, sections, slowTestThreshold ?? DEFAULT_OPTIONS.slowTestThreshold),
12718
- options: deriveOptions(sections),
12719
- sections,
12720
- tagGroups: this.buildTagGroups(data),
12721
- slowTests: slow.ranked,
12722
- baseCSS: this.fontsBundleCss + "\n" + this.readStyle("base.css"),
12723
- templateCSS: "",
12724
- chartjsScript: this.buildChartjsScript(),
12725
- watermarkPageHeightPx: PAGE_CONTENT_HEIGHT_PX,
12726
- releaseGateTally: buildFailureTally(data.analysis),
12727
- suiteChartLarge: data.charts.suiteResults.length > SUITE_CHART_SMALL_MAX,
12728
- suiteChartChunks: data.charts.suiteResults.length > SUITE_CHART_SMALL_MAX ? buildSuiteChartChunks(data.charts.suiteResults) : []
12729
- };
12730
- }
12731
- buildContext(data, template, userSections, slowTestThreshold) {
12732
- const baseCSS = this.fontsBundleCss + "\n" + this.readStyle("base.css");
12733
- const templateCSS = this.readStyle(`${template}.css`);
12734
- const sections = resolveSections(template, userSections);
12729
+ assembleContext(data, sections, templateCSS, opts) {
12735
12730
  const options = deriveOptions(sections);
12736
12731
  const slow = this.buildSlowTests(data);
12732
+ const tags = this.buildTagGroups(data, opts.requirementTagPattern);
12737
12733
  return {
12738
12734
  ...data,
12739
12735
  projects: this.enrichProjects(
12740
12736
  data,
12741
12737
  slow,
12742
12738
  sections,
12743
- slowTestThreshold ?? DEFAULT_OPTIONS.slowTestThreshold
12739
+ opts.slowTestThreshold ?? DEFAULT_OPTIONS.slowTestThreshold
12744
12740
  ),
12745
12741
  options,
12746
12742
  sections,
12747
- tagGroups: this.buildTagGroups(data),
12743
+ tagGroups: tags.all,
12744
+ requirementTagGroups: tags.requirements,
12745
+ categoryTagGroups: tags.categories,
12748
12746
  slowTests: slow.ranked,
12749
- baseCSS,
12747
+ baseCSS: this.fontsBundleCss + "\n" + this.readStyle("base.css"),
12750
12748
  templateCSS,
12751
12749
  chartjsScript: this.buildChartjsScript(),
12752
12750
  watermarkPageHeightPx: PAGE_CONTENT_HEIGHT_PX,
@@ -12758,7 +12756,7 @@ var TemplateEngine = class {
12758
12756
  buildChartjsScript() {
12759
12757
  return this.chartjsInline ? `<script>${this.chartjsInline}</script>` : "<!-- Chart.js not bundled; charts disabled --><script>window.__chartsReady=true;</script>";
12760
12758
  }
12761
- buildTagGroups(data) {
12759
+ buildTagGroups(data, requirementTagPattern) {
12762
12760
  const tagMap = /* @__PURE__ */ new Map();
12763
12761
  for (const project of data.projects) {
12764
12762
  for (const suite of this.flattenSuites(project.suites)) {
@@ -12773,11 +12771,20 @@ var TemplateEngine = class {
12773
12771
  }
12774
12772
  }
12775
12773
  }
12776
- return Array.from(tagMap.entries()).map(([tag, counts]) => ({
12777
- tag,
12778
- ...counts,
12779
- coveragePct: counts.total > 0 ? Math.round(counts.passed / counts.total * 100) : 0
12780
- }));
12774
+ const regex = new RegExp(requirementTagPattern ?? DEFAULT_OPTIONS.requirementTagPattern);
12775
+ const requirements = [];
12776
+ const categories = [];
12777
+ for (const [tag, counts] of tagMap) {
12778
+ const row = {
12779
+ tag,
12780
+ ...counts,
12781
+ coveragePct: counts.total > 0 ? Math.round(counts.passed / counts.total * 100) : 0
12782
+ };
12783
+ (regex.test(tag) ? requirements : categories).push(row);
12784
+ }
12785
+ requirements.sort((a, b) => NUMERIC_COLLATOR.compare(a.tag, b.tag));
12786
+ categories.sort((a, b) => b.total - a.total || a.tag.localeCompare(b.tag));
12787
+ return { all: [...requirements, ...categories], requirements, categories };
12781
12788
  }
12782
12789
  buildSlowTests(data) {
12783
12790
  const tests = [];
@@ -13534,7 +13541,12 @@ var PdfGenerator = class {
13534
13541
  }
13535
13542
  const templateLabel = source.kind === "builtin" ? source.id : source.label;
13536
13543
  logger.info(`Rendering template: ${templateLabel}`);
13537
- const html = source.kind === "builtin" ? this.templateEngine.render(renderData, source.id, options.sections, options.slowTestThreshold) : this.templateEngine.renderFromPath(renderData, source.absolutePath, options.slowTestThreshold);
13544
+ const renderOpts = {
13545
+ sections: options.sections,
13546
+ slowTestThreshold: options.slowTestThreshold,
13547
+ requirementTagPattern: options.requirementTagPattern
13548
+ };
13549
+ const html = source.kind === "builtin" ? this.templateEngine.render(renderData, source.id, renderOpts) : this.templateEngine.renderFromPath(renderData, source.absolutePath, renderOpts);
13538
13550
  const tempHtmlPath = await this.htmlWriter.write(html);
13539
13551
  const fileUrl = this.htmlWriter.toFileUrl(tempHtmlPath);
13540
13552
  try {
@@ -14402,7 +14414,7 @@ var PdfReporter = class {
14402
14414
  this.options = parseOptions(rawOptions);
14403
14415
  setLogLevel(this.options.logLevel);
14404
14416
  this.dataCollector = new DataCollector(this.options.capture);
14405
- this.version = true ? "0.21.0" : "0.x";
14417
+ this.version = true ? "0.22.1" : "0.x";
14406
14418
  logger.info(`@reportforge/playwright-pdf v${this.version} initialised`);
14407
14419
  this.licenseClient = new LicenseClient({
14408
14420
  licenseKey: this.options.licenseKey,
@@ -2,8 +2,10 @@
2
2
  <div class="section">
3
3
  <h2>Requirements Traceability</h2>
4
4
  <p style="font-size:10px;color:var(--text-3);margin-bottom:12px;">
5
- Tests grouped by annotation tags. Add <code>@tag('REQ-001')</code> to link tests to requirements.
5
+ {{#if requirementTagGroups.length}}Tests grouped by requirement ID tags.{{else}}No requirement ID tags found.{{/if}}
6
+ Add <code>{ tag: '@REQ-001' }</code> to link tests to requirements.
6
7
  </p>
8
+ {{#if requirementTagGroups.length}}
7
9
  <table>
8
10
  <thead>
9
11
  <tr>
@@ -11,13 +13,13 @@
11
13
  <th style="width:60px;">Tests</th>
12
14
  <th style="width:60px;">Passed</th>
13
15
  <th style="width:60px;">Failed</th>
14
- <th style="width:120px;">Coverage</th>
16
+ <th style="width:120px;">Pass Rate</th>
15
17
  </tr>
16
18
  </thead>
17
19
  <tbody>
18
- {{#each tagGroups}}
20
+ {{#each requirementTagGroups}}
19
21
  <tr class="no-break">
20
- <td><code style="font-size:9.5px;">{{tag}}</code></td>
22
+ <td><code class="req-matrix__tag" style="font-size:9.5px;">{{tag}}</code></td>
21
23
  <td>{{total}}</td>
22
24
  <td style="color:var(--pass);font-weight:600;">{{passed}}</td>
23
25
  <td style="color:{{#if failed}}var(--fail){{else}}var(--pass){{/if}};font-weight:600;">{{failed}}</td>
@@ -31,5 +33,19 @@
31
33
  {{/each}}
32
34
  </tbody>
33
35
  </table>
36
+ {{/if}}
37
+ {{#if categoryTagGroups.length}}
38
+ <div class="tag-summary no-break">
39
+ <div class="tag-summary__label">Tag Summary</div>
40
+ <div class="tag-summary__items">
41
+ {{#each categoryTagGroups}}
42
+ <span class="tag-summary__item{{#if failed}} tag-summary__item--fail{{/if}}">
43
+ <code class="tag-summary__tag">{{tag}}</code>
44
+ <span class="tag-summary__counts">{{passed}}/{{total}}</span>
45
+ </span>
46
+ {{/each}}
47
+ </div>
48
+ </div>
49
+ {{/if}}
34
50
  </div>
35
51
  {{/if}}
@@ -891,6 +891,29 @@ code {
891
891
  .coverage-bar__fill--mid { background: var(--timeout); }
892
892
  .coverage-bar__fill--high { background: var(--pass); }
893
893
 
894
+ /* ── Requirements traceability: category tag summary ─────────────────────── */
895
+ /* Non-requirement tags (suite-level categories like @regression) collapse
896
+ into one compact row list instead of repeating identical matrix rows. */
897
+ .tag-summary {
898
+ margin-top: 10px; padding-top: 10px;
899
+ border-top: 1px solid var(--border);
900
+ }
901
+ .tag-summary__label {
902
+ font-family: 'Inter', system-ui, sans-serif; font-weight: 600;
903
+ font-size: 8.5px; letter-spacing: 0.8px; text-transform: uppercase;
904
+ color: var(--text-3); margin-bottom: 4px;
905
+ }
906
+ .tag-summary__items { max-width: 340px; }
907
+ .tag-summary__item {
908
+ display: flex; justify-content: space-between; align-items: center;
909
+ padding: 3.5px 2px;
910
+ border-bottom: 1px solid var(--border);
911
+ }
912
+ .tag-summary__item:last-child { border-bottom: none; }
913
+ .tag-summary__tag { font-size: 9.5px; color: var(--text-1); background: none; border: none; padding: 0; }
914
+ .tag-summary__counts { font-size: 8.5px; font-weight: 600; color: var(--pass); }
915
+ .tag-summary__item--fail .tag-summary__counts { color: var(--fail); }
916
+
894
917
  /* ── Environment table — full ci-environment mode (fullEnvironment), shared ─── */
895
918
  .env-table {
896
919
  background: var(--surface);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reportforge/playwright-pdf",
3
- "version": "0.21.0",
3
+ "version": "0.22.1",
4
4
  "description": "Playwright Test reporter that generates designed PDF reports: minimal, detailed, and executive templates with CI/CD integrations",
5
5
  "license": "Elastic-2.0",
6
6
  "author": "ReportForge",
@@ -51,7 +51,6 @@
51
51
  "dist",
52
52
  "ci-templates",
53
53
  "README.md",
54
- "CHANGELOG.md",
55
54
  "LICENSE",
56
55
  "SECURITY.md"
57
56
  ],