@reportforge/playwright-pdf 0.4.1 → 0.5.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.
@@ -71,12 +71,14 @@
71
71
  var remaining = 0;
72
72
  function chartDone() { if (--remaining === 0) window.__chartsReady = true; }
73
73
 
74
- var passed = {{charts.passRate.passed}};
75
- var failed = {{charts.passRate.failed}};
76
- var skipped = {{charts.passRate.skipped}};
77
- var flaky = {{charts.passRate.flaky}};
78
- var total = passed + failed + skipped + flaky;
79
- var passRate = total > 0 ? Math.round((passed / total) * 100) : 0;
74
+ var passed = {{charts.passRate.passed}};
75
+ var failed = {{charts.passRate.failed}};
76
+ var timedOut = {{charts.passRate.timedOut}};
77
+ var skipped = {{charts.passRate.skipped}};
78
+ var flaky = {{charts.passRate.flaky}};
79
+ // Pass rate is the canonical server-side stat (flaky counts as pass, denominator
80
+ // is every test) so the doughnut centre can never disagree with the KPI strip.
81
+ var passRate = {{stats.passRate}};
80
82
 
81
83
  /* ── Centre-text plugin for the doughnut ─────────────────────────────── */
82
84
  var centerTextPlugin = {
@@ -107,11 +109,11 @@
107
109
  new Chart(document.getElementById('passRateChart'), {
108
110
  type: 'doughnut',
109
111
  data: {
110
- labels: ['Passed', 'Failed', 'Skipped', 'Flaky'],
112
+ labels: ['Passed', 'Failed', 'Timed Out', 'Skipped', 'Flaky'],
111
113
  datasets: [{
112
- data: [passed, failed, skipped, flaky],
113
- backgroundColor: ['#3F7D58', '#C3553C', '#7A786E', '#D49452'],
114
- borderColor: ['#FAF9F5', '#FAF9F5', '#FAF9F5', '#FAF9F5'],
114
+ data: [passed, failed, timedOut, skipped, flaky],
115
+ backgroundColor: ['#3F7D58', '#C3553C', '#B5803A', '#7A786E', '#D49452'],
116
+ borderColor: ['#FAF9F5', '#FAF9F5', '#FAF9F5', '#FAF9F5', '#FAF9F5'],
115
117
  borderWidth: 2,
116
118
  hoverOffset: 4
117
119
  }]
@@ -135,8 +137,9 @@
135
137
  /* ── Horizontal suite results bar ────────────────────────────────────── */
136
138
  var suiteLabels = [{{{jsonArray charts.suiteResults 'label'}}}];
137
139
  var suitePassed = [{{{jsonArray charts.suiteResults 'passed'}}}];
138
- var suiteFailed = [{{{jsonArray charts.suiteResults 'failed'}}}];
139
- var suiteSkipped = [{{{jsonArray charts.suiteResults 'skipped'}}}];
140
+ var suiteFailed = [{{{jsonArray charts.suiteResults 'failed'}}}];
141
+ var suiteTimedOut = [{{{jsonArray charts.suiteResults 'timedOut'}}}];
142
+ var suiteSkipped = [{{{jsonArray charts.suiteResults 'skipped'}}}];
140
143
 
141
144
  remaining += 1;
142
145
  new Chart(document.getElementById('suitesBarChart'), {
@@ -144,9 +147,10 @@
144
147
  data: {
145
148
  labels: suiteLabels,
146
149
  datasets: [
147
- { label: 'Passed', data: suitePassed, backgroundColor: '#3F7D58', borderRadius: 3 },
148
- { label: 'Failed', data: suiteFailed, backgroundColor: '#C3553C', borderRadius: 3 },
149
- { label: 'Skipped', data: suiteSkipped, backgroundColor: '#7A786E', borderRadius: 3 }
150
+ { label: 'Passed', data: suitePassed, backgroundColor: '#3F7D58', borderRadius: 3 },
151
+ { label: 'Failed', data: suiteFailed, backgroundColor: '#C3553C', borderRadius: 3 },
152
+ { label: 'Timed Out', data: suiteTimedOut, backgroundColor: '#B5803A', borderRadius: 3 },
153
+ { label: 'Skipped', data: suiteSkipped, backgroundColor: '#7A786E', borderRadius: 3 }
150
154
  ]
151
155
  },
152
156
  options: {
@@ -161,7 +165,17 @@
161
165
  },
162
166
  y: {
163
167
  stacked: true,
164
- ticks: { font: { size: 9, family: 'Inter, sans-serif' }, color: '#1F1F1C' },
168
+ // Widen the label gutter so the longest label isn't clipped at the canvas edge.
169
+ afterFit: function(scale) { if (!scale.$rfPadded) { scale.$rfPadded = true; scale.width += 8; } },
170
+ ticks: {
171
+ font: { size: 9, family: 'Inter, sans-serif' }, color: '#1F1F1C', autoSkip: false,
172
+ // Truncate long suite / describe labels so they fit the axis gutter
173
+ // instead of clipping at the canvas edge.
174
+ callback: function(value) {
175
+ var l = this.getLabelForValue(value);
176
+ return l.length > 24 ? l.slice(0, 23) + '…' : l;
177
+ }
178
+ },
165
179
  grid: { display: false },
166
180
  border: { color: '#D6D4C8' }
167
181
  }
@@ -9,7 +9,7 @@
9
9
  <div class="cover-page__divider"></div>
10
10
 
11
11
  <span class="cover-page__verdict cover-page__verdict--{{stats.verdict}}">
12
- {{upperCase stats.verdict}}
12
+ {{verdictLabel stats.verdict}}
13
13
  </span>
14
14
 
15
15
  <div class="cover-page__stats-strip">
@@ -1,6 +1,6 @@
1
1
  <div class="section">
2
2
  <span class="verdict verdict--{{stats.verdict}}">
3
- {{upperCase stats.verdict}}
3
+ {{verdictLabel stats.verdict}}
4
4
  </span>
5
5
 
6
6
  <div class="kpi-strip">
@@ -19,6 +19,10 @@
19
19
  <div class="kpi-card__value">{{stats.failed}}</div>
20
20
  <div class="kpi-card__label">Failed</div>
21
21
  </div>
22
+ <div class="kpi-card kpi-card--timedout">
23
+ <div class="kpi-card__value">{{stats.timedOut}}</div>
24
+ <div class="kpi-card__label">Timed Out</div>
25
+ </div>
22
26
  <div class="kpi-card kpi-card--skip">
23
27
  <div class="kpi-card__value">{{stats.skipped}}</div>
24
28
  <div class="kpi-card__label">Skipped</div>
@@ -1,5 +1,5 @@
1
1
  {{#if failures.length}}
2
- <div class="section page-break">
2
+ <div class="section">
3
3
  <h2>Failures ({{failures.length}}{{#if overflowFailureCount}} shown · {{overflowFailureCount}} more in sidecar{{/if}})</h2>
4
4
  {{#if overflowFailureCount}}
5
5
  <div class="overflow-banner">
@@ -9,7 +9,7 @@
9
9
  </div>
10
10
  {{/if}}
11
11
  {{#each failures}}
12
- <div class="failure-item no-break">
12
+ <div class="failure-item failure-item--{{severity}} no-break">
13
13
  {{#if ../options.showFullFailures}}
14
14
 
15
15
  <div class="failure-item__header">
@@ -23,7 +23,7 @@
23
23
  <td style="color:{{#if failed}}var(--fail){{else}}var(--pass){{/if}};font-weight:600;">{{failed}}</td>
24
24
  <td>
25
25
  <div class="coverage-bar">
26
- <div class="coverage-bar__fill" style="width:{{coveragePct}}%;"></div>
26
+ <div class="coverage-bar__fill coverage-bar__fill--{{coverageBand coveragePct}}" style="width:{{coveragePct}}%;"></div>
27
27
  </div>
28
28
  <span style="font-size:9.5px;color:var(--text-3);">{{coveragePct}}%</span>
29
29
  </td>
@@ -16,9 +16,9 @@
16
16
  <tr class="no-break">
17
17
  <td style="font-weight:500;">{{title}}</td>
18
18
  <td style="font-size:9px;color:var(--text-3);font-family:'JetBrains Mono',monospace;">{{suiteTitle}}</td>
19
- {{#if ../showProject}}<td style="font-size:9px;color:var(--text-3);">{{project}}</td>{{/if}}
19
+ {{#if ../showProject}}<td style="font-size:9px;color:var(--text-3);white-space:nowrap;">{{project}}</td>{{/if}}
20
20
  <td style="font-family:'JetBrains Mono',monospace;font-size:9.5px;color:var(--text-2);text-align:right;">{{durationFormatted}}</td>
21
- <td><span class="badge badge--{{status}}">{{status}}</span></td>
21
+ <td>{{statusBadge status}}</td>
22
22
  </tr>
23
23
  {{/each}}
24
24
  </tbody>
@@ -15,6 +15,7 @@
15
15
  <span class="suite-stats">
16
16
  <span class="badge badge--passed">{{stats.passed}}</span>
17
17
  {{#if stats.failed}}<span class="badge badge--failed">{{stats.failed}}</span>{{/if}}
18
+ {{#if stats.timedOut}}<span class="badge badge--timedout">{{stats.timedOut}}</span>{{/if}}
18
19
  {{#if stats.skipped}}<span class="badge badge--skipped">{{stats.skipped}}</span>{{/if}}
19
20
  {{#if stats.flaky}}<span class="badge badge--flaky">{{stats.flaky}}</span>{{/if}}
20
21
  </span>
@@ -41,7 +42,7 @@
41
42
  </div>
42
43
  {{/if}}
43
44
  </td>
44
- <td><span class="badge badge--{{status}}">{{status}}</span></td>
45
+ <td>{{statusBadge status}}</td>
45
46
  <td style="color:var(--text-2);font-family:'JetBrains Mono',monospace;font-size:9.5px;">{{formatDuration duration}}</td>
46
47
  {{#if ../../options.showRetries}}<td style="color:var(--text-3);text-align:center;">{{retryCount}}</td>{{/if}}
47
48
  </tr>
@@ -149,14 +149,19 @@ tr:last-child td { border-bottom: none; }
149
149
  }
150
150
 
151
151
  /* ── Page breaks ────────────────────────────────────────────────────────── */
152
- .page-break { page-break-before: always; }
153
- .no-break { page-break-inside: avoid; }
154
- .page-group { page-break-before: always; page-break-inside: avoid; break-inside: avoid; }
155
- .page-group > .section { page-break-inside: avoid; break-inside: avoid; }
156
- .page-group > .section:last-child { margin-bottom: 0; }
152
+ .no-break { page-break-inside: avoid; }
157
153
 
158
154
  /* ── Section wrapper ────────────────────────────────────────────────────── */
159
- .section { margin-bottom: 24px; page-break-inside: avoid; }
155
+ /* Large sections (suite breakdown, failures, tables) MAY flow across pages —
156
+ their rows carry .no-break so they never split mid-row. Only the small atomic
157
+ blocks below stay glued together, and headings stick to the content beneath. */
158
+ .section { margin-bottom: 24px; }
159
+ .kpi-strip, .summary-row, .charts-grid, .release-gate, .flaky-callout,
160
+ .trend-card, .flakiness-card, .cover-page__stats-strip, .env-grid {
161
+ page-break-inside: avoid; break-inside: avoid;
162
+ }
163
+ h2, h3 { page-break-after: avoid; break-after: avoid; }
164
+ .suite-group__header { page-break-after: avoid; break-after: avoid; }
160
165
 
161
166
  /* ── Code / stack trace ─────────────────────────────────────────────────── */
162
167
  pre, code {
@@ -235,3 +240,236 @@ code {
235
240
 
236
241
  /* ── Failure-analysis one-liner — shared by detailed + executive templates ── */
237
242
  .analysis-oneliner { font-size: 0.9em; opacity: 0.9; margin-top: 4px; }
243
+
244
+ /* ── Release gate — ship/hold banner, shared by all templates ────────────── */
245
+ .release-gate {
246
+ display: flex; align-items: center; gap: 16px;
247
+ padding: 16px 20px; border-radius: 6px; margin-bottom: 22px;
248
+ border: 1px solid;
249
+ position: relative; overflow: hidden;
250
+ -webkit-print-color-adjust: exact;
251
+ print-color-adjust: exact;
252
+ }
253
+ .release-gate--pass {
254
+ background: var(--pass-dim); border-color: #C5DBC9;
255
+ }
256
+ .release-gate--fail {
257
+ background: var(--fail-dim); border-color: #E5C9BD;
258
+ }
259
+ .release-gate__icon {
260
+ font-size: 22px; font-weight: 700; flex-shrink: 0; width: 32px;
261
+ text-align: center; line-height: 1;
262
+ }
263
+ .release-gate--pass .release-gate__icon { color: var(--pass); }
264
+ .release-gate--fail .release-gate__icon { color: var(--fail); }
265
+ .release-gate__body { flex: 1; }
266
+ .release-gate__label {
267
+ font-family: 'Inter', system-ui, sans-serif; font-size: 8px;
268
+ font-weight: 700; letter-spacing: 1.4px; text-transform: uppercase;
269
+ color: var(--text-3); margin-bottom: 4px;
270
+ }
271
+ .release-gate__verdict {
272
+ font-family: 'Source Serif Pro', Georgia, serif;
273
+ font-size: 18px; font-weight: 700; letter-spacing: -0.2px; line-height: 1.1;
274
+ }
275
+ .release-gate__verdict--pass { color: var(--pass); }
276
+ .release-gate__verdict--fail { color: var(--fail); }
277
+ .release-gate__detail {
278
+ font-family: 'Inter', system-ui, sans-serif; font-size: 10px;
279
+ color: var(--text-2); margin-top: 4px;
280
+ }
281
+ .release-gate__meta {
282
+ display: flex; flex-direction: column; gap: 3px; align-items: flex-end;
283
+ font-family: 'JetBrains Mono', monospace; font-size: 9px; color: var(--text-3);
284
+ flex-shrink: 0; text-align: right;
285
+ }
286
+ .release-gate__sep { color: var(--border-3); }
287
+ .release-gate__flaky-warn { color: var(--flaky); font-weight: 600; }
288
+
289
+ /* ── Failure severity — colour the card's left edge by triage rank ───────── */
290
+ /* Two-class specificity beats the per-template `.failure-item` border shorthand. */
291
+ .failure-item.failure-item--critical { border-left-color: var(--fail); }
292
+ .failure-item.failure-item--high { border-left-color: #A35E1E; }
293
+ .failure-item.failure-item--medium { border-left-color: var(--timeout); }
294
+ .failure-item.failure-item--low { border-left-color: var(--text-3); }
295
+
296
+ /* ── History trend card — shared by detailed + executive (charts.hbs) ─────── */
297
+ .trend-card {
298
+ background: var(--surface);
299
+ border: 1px solid var(--border-2);
300
+ border-radius: 6px;
301
+ padding: 16px 18px 14px;
302
+ margin-bottom: 24px;
303
+ page-break-inside: avoid;
304
+ }
305
+ .trend-card__header {
306
+ display: flex; align-items: center; justify-content: space-between;
307
+ margin-bottom: 14px;
308
+ }
309
+ .trend-card__title {
310
+ font-family: 'Inter', system-ui, sans-serif;
311
+ font-size: 9px; font-weight: 700;
312
+ letter-spacing: 1.2px; text-transform: uppercase;
313
+ color: var(--text-3);
314
+ }
315
+ .delta { font-size: 11px; font-weight: 700; }
316
+ .delta-up { color: var(--pass); }
317
+ .delta-down { color: var(--fail); }
318
+
319
+ /* ── Run history table (generated by JS) ─────────────────────────────────── */
320
+ .trend-run-history {
321
+ margin-top: 10px;
322
+ border-top: 1px solid var(--border);
323
+ padding-top: 8px;
324
+ }
325
+ .rh-item {
326
+ display: flex; align-items: center; gap: 12px;
327
+ padding: 5px 8px; border-radius: 4px;
328
+ font-size: 9px; font-family: 'Inter', system-ui, sans-serif;
329
+ }
330
+ .rh-item:nth-child(odd) {
331
+ background: var(--surface-2);
332
+ -webkit-print-color-adjust: exact; print-color-adjust: exact;
333
+ }
334
+ .rh-dot { flex-shrink: 0; }
335
+ .rh-date { color: var(--text-2); flex: 1; }
336
+ .rh-rate { font-weight: 700; min-width: 34px; color: var(--text-1); }
337
+ .rh-rate--pass { color: var(--pass); }
338
+ .rh-rate--fail { color: var(--fail); }
339
+ .rh-rate--partial { color: var(--flaky); }
340
+ .rh-tests { color: var(--text-3); min-width: 58px; }
341
+ .rh-dur { color: var(--text-3); }
342
+
343
+ /* ── Verdict dots (used in run history rh-dot) ───────────────────────────── */
344
+ .verdict-dot { width: 8px; height: 8px; border-radius: 50%; display: inline-block; }
345
+ .verdict-passed { background: var(--pass); -webkit-print-color-adjust: exact; print-color-adjust: exact; }
346
+ .verdict-partial { background: var(--flaky); -webkit-print-color-adjust: exact; print-color-adjust: exact; }
347
+ .verdict-failed { background: var(--fail); -webkit-print-color-adjust: exact; print-color-adjust: exact; }
348
+ .verdict-unknown { background: var(--skip); -webkit-print-color-adjust: exact; print-color-adjust: exact; }
349
+
350
+ /* ── Flakiness trend table (R8) ─────────────────────────────────────────── */
351
+ .flakiness-card {
352
+ margin: 24px 0 0;
353
+ border: 1px solid var(--border);
354
+ border-radius: 8px;
355
+ overflow: hidden;
356
+ }
357
+
358
+ .flakiness-card__header {
359
+ display: flex;
360
+ align-items: center;
361
+ padding: 10px 16px;
362
+ background: var(--surface-2);
363
+ border-bottom: 1px solid var(--border);
364
+ }
365
+
366
+ .flakiness-card__title {
367
+ font-size: 11px;
368
+ font-weight: 600;
369
+ text-transform: uppercase;
370
+ letter-spacing: 0.04em;
371
+ color: var(--text-3);
372
+ }
373
+
374
+ .flakiness-table {
375
+ width: 100%;
376
+ border-collapse: collapse;
377
+ font-size: 11px;
378
+ }
379
+
380
+ .flakiness-table thead th {
381
+ padding: 8px 12px;
382
+ text-align: left;
383
+ font-weight: 600;
384
+ font-size: 10px;
385
+ text-transform: uppercase;
386
+ letter-spacing: 0.04em;
387
+ color: var(--text-3);
388
+ border-bottom: 1px solid var(--border);
389
+ background: var(--surface-2);
390
+ }
391
+
392
+ .flakiness-table tbody tr {
393
+ border-bottom: 1px solid var(--border);
394
+ }
395
+
396
+ .flakiness-table tbody tr:last-child {
397
+ border-bottom: none;
398
+ }
399
+
400
+ .flakiness-table tbody td {
401
+ padding: 8px 12px;
402
+ vertical-align: middle;
403
+ color: var(--text-1);
404
+ }
405
+
406
+ .flakiness-table__test {
407
+ max-width: 300px;
408
+ overflow: hidden;
409
+ text-overflow: ellipsis;
410
+ white-space: nowrap;
411
+ }
412
+
413
+ .flakiness-table__rate {
414
+ width: 72px;
415
+ font-weight: 600;
416
+ color: var(--flaky);
417
+ }
418
+
419
+ .flakiness-table__count {
420
+ width: 72px;
421
+ color: var(--text-3);
422
+ }
423
+
424
+ .flakiness-table__spark {
425
+ width: 140px;
426
+ }
427
+
428
+ .flaky-spark {
429
+ display: flex;
430
+ gap: 3px;
431
+ align-items: center;
432
+ }
433
+
434
+ .flaky-dot {
435
+ flex-shrink: 0;
436
+ width: 7px;
437
+ height: 7px;
438
+ border-radius: 50%;
439
+ background: var(--border, #e5e3d8);
440
+ print-color-adjust: exact;
441
+ -webkit-print-color-adjust: exact;
442
+ }
443
+
444
+ .flaky-dot--hit {
445
+ background: #f59e0b;
446
+ print-color-adjust: exact;
447
+ -webkit-print-color-adjust: exact;
448
+ }
449
+
450
+ /* ── Environment grid — compact ci-environment, shared by minimal + executive ─ */
451
+ .env-grid {
452
+ display: grid; grid-template-columns: 1fr 1fr;
453
+ gap: 0;
454
+ background: var(--surface);
455
+ border: 1px solid var(--border-2);
456
+ border-radius: 6px;
457
+ overflow: hidden;
458
+ }
459
+ .env-item {
460
+ display: flex; gap: 12px; font-size: 10.5px; align-items: baseline;
461
+ padding: 10px 18px;
462
+ border-bottom: 1px solid var(--border);
463
+ border-right: 1px solid var(--border);
464
+ }
465
+ .env-item:nth-child(even) { border-right: none; }
466
+ .env-item:nth-last-child(-n+2) { border-bottom: none; }
467
+ .env-item__key {
468
+ font-family: 'Inter', system-ui, sans-serif; font-weight: 600;
469
+ font-size: 8.5px; letter-spacing: 1px; text-transform: uppercase;
470
+ color: var(--text-2); min-width: 80px;
471
+ }
472
+ .env-item__val {
473
+ font-family: 'JetBrains Mono', monospace;
474
+ font-size: 9.5px; color: var(--text-1);
475
+ }
@@ -57,7 +57,7 @@
57
57
 
58
58
  /* ── KPI strip ───────────────────────────────────────────────────────────── */
59
59
  .kpi-strip {
60
- display: grid; grid-template-columns: repeat(5, 1fr);
60
+ display: grid; grid-template-columns: repeat(6, 1fr);
61
61
  gap: 10px; margin-bottom: 22px;
62
62
  }
63
63
  .kpi-card {
@@ -74,6 +74,7 @@
74
74
  .kpi-card--total::before { background: var(--accent); }
75
75
  .kpi-card--pass::before { background: var(--pass); }
76
76
  .kpi-card--fail::before { background: var(--fail); }
77
+ .kpi-card--timedout::before { background: var(--timeout); }
77
78
  .kpi-card--skip::before { background: var(--skip); }
78
79
  .kpi-card--flaky::before { background: var(--flaky); }
79
80
 
@@ -87,6 +88,7 @@
87
88
  .kpi-card--total .kpi-card__value { color: var(--info); }
88
89
  .kpi-card--pass .kpi-card__value { color: var(--pass); }
89
90
  .kpi-card--fail .kpi-card__value { color: var(--fail); }
91
+ .kpi-card--timedout .kpi-card__value { color: var(--timeout); }
90
92
  .kpi-card--skip .kpi-card__value { color: var(--skip); }
91
93
  .kpi-card--flaky .kpi-card__value { color: var(--flaky); }
92
94
 
@@ -230,10 +232,12 @@
230
232
  }
231
233
  .coverage-bar__fill {
232
234
  height: 100%; border-radius: 3px;
233
- background: var(--pass);
234
235
  -webkit-print-color-adjust: exact;
235
236
  print-color-adjust: exact;
236
237
  }
238
+ .coverage-bar__fill--low { background: var(--fail); }
239
+ .coverage-bar__fill--mid { background: var(--timeout); }
240
+ .coverage-bar__fill--high { background: var(--pass); }
237
241
 
238
242
  /* ── Environment table ───────────────────────────────────────────────────── */
239
243
  .env-table {
@@ -254,160 +258,6 @@
254
258
  font-size: 9.5px; color: var(--text-1);
255
259
  }
256
260
 
257
- /* ── History trend card ──────────────────────────────────────────────────── */
258
- .trend-card {
259
- background: var(--surface);
260
- border: 1px solid var(--border-2);
261
- border-radius: 6px;
262
- padding: 16px 18px 14px;
263
- margin-bottom: 24px;
264
- page-break-inside: avoid;
265
- }
266
- .trend-card__header {
267
- display: flex; align-items: center; justify-content: space-between;
268
- margin-bottom: 14px;
269
- }
270
- .trend-card__title {
271
- font-family: 'Inter', system-ui, sans-serif;
272
- font-size: 9px; font-weight: 700;
273
- letter-spacing: 1.2px; text-transform: uppercase;
274
- color: var(--text-3);
275
- }
276
- .delta { font-size: 11px; font-weight: 700; }
277
- .delta-up { color: var(--pass); }
278
- .delta-down { color: var(--fail); }
279
-
280
- /* ── Run history table (generated by JS) ─────────────────────────────────── */
281
- .trend-run-history {
282
- margin-top: 10px;
283
- border-top: 1px solid var(--border);
284
- padding-top: 8px;
285
- }
286
- .rh-item {
287
- display: flex; align-items: center; gap: 12px;
288
- padding: 5px 8px; border-radius: 4px;
289
- font-size: 9px; font-family: 'Inter', system-ui, sans-serif;
290
- }
291
- .rh-item:nth-child(odd) {
292
- background: var(--surface-2);
293
- -webkit-print-color-adjust: exact; print-color-adjust: exact;
294
- }
295
- .rh-dot { flex-shrink: 0; }
296
- .rh-date { color: var(--text-2); flex: 1; }
297
- .rh-rate { font-weight: 700; min-width: 34px; color: var(--text-1); }
298
- .rh-rate--pass { color: var(--pass); }
299
- .rh-rate--fail { color: var(--fail); }
300
- .rh-rate--partial { color: var(--flaky); }
301
- .rh-tests { color: var(--text-3); min-width: 58px; }
302
- .rh-dur { color: var(--text-3); }
303
-
304
- /* ── Verdict dots (used in run history rh-dot) ───────────────────────────── */
305
- .verdict-dot { width: 8px; height: 8px; border-radius: 50%; display: inline-block; }
306
- .verdict-passed { background: var(--pass); -webkit-print-color-adjust: exact; print-color-adjust: exact; }
307
- .verdict-partial { background: var(--flaky); -webkit-print-color-adjust: exact; print-color-adjust: exact; }
308
- .verdict-failed { background: var(--fail); -webkit-print-color-adjust: exact; print-color-adjust: exact; }
309
- .verdict-unknown { background: var(--skip); -webkit-print-color-adjust: exact; print-color-adjust: exact; }
310
-
311
- /* ── Flakiness trend table (R8) ─────────────────────────────────────────── */
312
- .flakiness-card {
313
- margin: 24px 0 0;
314
- border: 1px solid var(--border);
315
- border-radius: 8px;
316
- overflow: hidden;
317
- }
318
-
319
- .flakiness-card__header {
320
- display: flex;
321
- align-items: center;
322
- padding: 10px 16px;
323
- background: var(--surface-2);
324
- border-bottom: 1px solid var(--border);
325
- }
326
-
327
- .flakiness-card__title {
328
- font-size: 11px;
329
- font-weight: 600;
330
- text-transform: uppercase;
331
- letter-spacing: 0.04em;
332
- color: var(--text-3);
333
- }
334
-
335
- .flakiness-table {
336
- width: 100%;
337
- border-collapse: collapse;
338
- font-size: 11px;
339
- }
340
-
341
- .flakiness-table thead th {
342
- padding: 8px 12px;
343
- text-align: left;
344
- font-weight: 600;
345
- font-size: 10px;
346
- text-transform: uppercase;
347
- letter-spacing: 0.04em;
348
- color: var(--text-3);
349
- border-bottom: 1px solid var(--border);
350
- background: var(--surface-2);
351
- }
352
-
353
- .flakiness-table tbody tr {
354
- border-bottom: 1px solid var(--border);
355
- }
356
-
357
- .flakiness-table tbody tr:last-child {
358
- border-bottom: none;
359
- }
360
-
361
- .flakiness-table tbody td {
362
- padding: 8px 12px;
363
- vertical-align: middle;
364
- color: var(--text-1);
365
- }
366
-
367
- .flakiness-table__test {
368
- max-width: 300px;
369
- overflow: hidden;
370
- text-overflow: ellipsis;
371
- white-space: nowrap;
372
- }
373
-
374
- .flakiness-table__rate {
375
- width: 72px;
376
- font-weight: 600;
377
- color: var(--flaky);
378
- }
379
-
380
- .flakiness-table__count {
381
- width: 72px;
382
- color: var(--text-3);
383
- }
384
-
385
- .flakiness-table__spark {
386
- width: 140px;
387
- }
388
-
389
- .flaky-spark {
390
- display: flex;
391
- gap: 3px;
392
- align-items: center;
393
- }
394
-
395
- .flaky-dot {
396
- flex-shrink: 0;
397
- width: 7px;
398
- height: 7px;
399
- border-radius: 50%;
400
- background: var(--border, #e5e3d8);
401
- print-color-adjust: exact;
402
- -webkit-print-color-adjust: exact;
403
- }
404
-
405
- .flaky-dot--hit {
406
- background: #f59e0b;
407
- print-color-adjust: exact;
408
- -webkit-print-color-adjust: exact;
409
- }
410
-
411
261
  /* ── Failure Analysis ───────────────────────────────────────────────────── */
412
262
  /* NOTE: .analysis-oneliner lives in base.css — the executive template renders it too. */
413
263
  .failure-analysis { margin-top: 24px; }
@@ -428,6 +278,3 @@
428
278
  .cluster--env-config { border-color: oklch(55% 0.15 145); }
429
279
  .cluster--unknown { border-color: oklch(60% 0.00 0); }
430
280
  .confidence-warning { font-size: 0.8em; opacity: 0.75; }
431
-
432
- </content>
433
- </invoke>