@kaizenreport/kensho-viewer 0.1.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.
@@ -0,0 +1,1472 @@
1
+ /* Auto-generated from pages.jsx by packages/viewer/scripts/build.js. Edit the .jsx — DO NOT edit this file. */
2
+ /* global React, TreeDetailPage, TrendChartV2, HBars, DurationHistogram, FlakeScatter, TimelineGantt, SuiteHeatmap, RetryWaterfall */
3
+ const {
4
+ useState: useStateP
5
+ } = React;
6
+
7
+ // ============== GRAPHS PAGE ==============
8
+ function GraphsPage() {
9
+ const TREND_RUNS = window.TREND_RUNS || [];
10
+ const HISTOGRAM = window.HISTOGRAM || [];
11
+ const RICH_TESTS = window.RICH_TESTS || {};
12
+ const CATEGORIES = window.CATEGORIES || [];
13
+ const fmt = window._kenshoFmtDuration || (ms => ms + 'ms');
14
+ const [showAllSuites, setShowAllSuites] = useStateP(false);
15
+ const SUITE_CAP = 12;
16
+
17
+ // Highlight banner derivations — surface the three "punchiest" facts
18
+ // about the run so a stakeholder can scan and act in 5 seconds.
19
+ const allRich = Object.values(RICH_TESTS);
20
+ const slowest = allRich.filter(t => t.durMs > 0).sort((a, b) => b.durMs - a.durMs)[0];
21
+ const mostRetried = allRich.filter(t => t.retries > 0).sort((a, b) => b.retries - a.retries)[0];
22
+ const topCategory = CATEGORIES[0];
23
+
24
+ // Derive "Status by suite" from RICH_TESTS, grouped by first suite segment.
25
+ const suiteBuckets = {};
26
+ Object.values(RICH_TESTS).forEach(t => {
27
+ const suiteName = (t.suite || '').split('›')[0].trim() || 'Default';
28
+ if (!suiteBuckets[suiteName]) suiteBuckets[suiteName] = {
29
+ passed: 0,
30
+ failed: 0,
31
+ broken: 0,
32
+ skipped: 0
33
+ };
34
+ if (suiteBuckets[suiteName][t.status] != null) suiteBuckets[suiteName][t.status]++;
35
+ });
36
+ const suiteStatusBars = Object.entries(suiteBuckets).map(([label, b]) => {
37
+ const segs = ['failed', 'broken', 'skipped', 'passed'].filter(k => b[k] > 0).map(k => ({
38
+ k,
39
+ n: b[k]
40
+ }));
41
+ const total = b.passed + b.failed + b.broken + b.skipped;
42
+ return {
43
+ label,
44
+ segs,
45
+ total,
46
+ failures: b.failed + b.broken
47
+ };
48
+ }).sort((a, b) => b.failures - a.failures || b.total - a.total);
49
+ const visibleSuites = showAllSuites ? suiteStatusBars : suiteStatusBars.slice(0, SUITE_CAP);
50
+ const hiddenSuiteCount = suiteStatusBars.length - visibleSuites.length;
51
+ const totalTests = Object.values(RICH_TESTS).length;
52
+ const hasFlake = Object.values(RICH_TESTS).some(t => t.flakeRate > 0);
53
+ return /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement("h1", {
54
+ className: "k-h1",
55
+ style: {
56
+ marginBottom: 4
57
+ }
58
+ }, "Graphs"), /*#__PURE__*/React.createElement("div", {
59
+ className: "k-meta",
60
+ style: {
61
+ marginBottom: 18
62
+ }
63
+ }, "Distributions and trends across the active run \xB7 ", totalTests, " tests"), (slowest || mostRetried || topCategory) && /*#__PURE__*/React.createElement("div", {
64
+ style: {
65
+ display: 'grid',
66
+ gridTemplateColumns: 'repeat(3, 1fr)',
67
+ gap: 16,
68
+ marginBottom: 16
69
+ }
70
+ }, slowest ? /*#__PURE__*/React.createElement(HighlightStat, {
71
+ overline: "Slowest test",
72
+ value: slowest.dur,
73
+ valueColor: "var(--status-broken-fg)",
74
+ subtitle: slowest.name,
75
+ accent: "var(--status-broken)",
76
+ onClick: () => window.__openTest?.(slowest.id),
77
+ title: `Open ${slowest.name} →`
78
+ }) : /*#__PURE__*/React.createElement(HighlightStat, {
79
+ overline: "Slowest test",
80
+ value: "\u2014",
81
+ subtitle: "No timing data",
82
+ accent: "var(--line)"
83
+ }), mostRetried ? /*#__PURE__*/React.createElement(HighlightStat, {
84
+ overline: "Most retried",
85
+ value: `${mostRetried.retries}×`,
86
+ valueColor: "#B69CFF",
87
+ subtitle: mostRetried.name,
88
+ accent: "#7C5CFF",
89
+ onClick: () => window.__openTest?.(mostRetried.id),
90
+ title: `Open ${mostRetried.name} →`
91
+ }) : /*#__PURE__*/React.createElement(HighlightStat, {
92
+ overline: "Most retried",
93
+ value: "0",
94
+ subtitle: "No retries this run \u2014 clean execution",
95
+ accent: "var(--line)"
96
+ }), topCategory ? /*#__PURE__*/React.createElement(HighlightStat, {
97
+ overline: "Top failure category",
98
+ value: String(topCategory.count),
99
+ valueColor: "var(--status-failed-fg)",
100
+ subtitle: topCategory.kind,
101
+ accent: "var(--status-failed)",
102
+ onClick: () => window.__navTo?.('categories'),
103
+ title: "Open Categories \u2192"
104
+ }) : /*#__PURE__*/React.createElement(HighlightStat, {
105
+ overline: "Top failure category",
106
+ value: "0",
107
+ subtitle: "No failures this run",
108
+ accent: "var(--status-passed)"
109
+ })), /*#__PURE__*/React.createElement("div", {
110
+ style: {
111
+ display: 'grid',
112
+ gridTemplateColumns: '1fr 1fr',
113
+ gap: 16
114
+ }
115
+ }, /*#__PURE__*/React.createElement("div", {
116
+ className: "card",
117
+ style: {
118
+ gridColumn: 'span 2'
119
+ }
120
+ }, /*#__PURE__*/React.createElement("div", {
121
+ className: "hd"
122
+ }, /*#__PURE__*/React.createElement("h3", null, "Trend \xB7 ", TREND_RUNS.length, " run", TREND_RUNS.length === 1 ? '' : 's'), /*#__PURE__*/React.createElement("div", {
123
+ className: "meta"
124
+ }, "stacked status counts")), TREND_RUNS.length > 0 ? /*#__PURE__*/React.createElement(TrendChartV2, {
125
+ runs: TREND_RUNS
126
+ }) : /*#__PURE__*/React.createElement("div", {
127
+ style: {
128
+ padding: 30,
129
+ color: 'var(--fg3)',
130
+ textAlign: 'center',
131
+ fontFamily: 'var(--font-mono)',
132
+ fontSize: 12
133
+ }
134
+ }, "No run history yet. Run kensho generate over multiple runs to populate.")), /*#__PURE__*/React.createElement("div", {
135
+ className: "card"
136
+ }, /*#__PURE__*/React.createElement("div", {
137
+ className: "hd"
138
+ }, /*#__PURE__*/React.createElement("h3", null, "Status by suite"), /*#__PURE__*/React.createElement("div", {
139
+ className: "meta"
140
+ }, showAllSuites ? `all ${suiteStatusBars.length}` : `top ${visibleSuites.length} of ${suiteStatusBars.length}`, " \xB7 sorted by failures")), /*#__PURE__*/React.createElement("div", {
141
+ style: {
142
+ display: 'flex',
143
+ flexDirection: 'column',
144
+ gap: 8
145
+ }
146
+ }, visibleSuites.map((s, i) => /*#__PURE__*/React.createElement("div", {
147
+ key: i,
148
+ style: {
149
+ display: 'grid',
150
+ gridTemplateColumns: '160px 1fr 30px',
151
+ alignItems: 'center',
152
+ gap: 10
153
+ }
154
+ }, /*#__PURE__*/React.createElement("div", {
155
+ style: {
156
+ fontFamily: 'var(--font-mono)',
157
+ fontSize: 12,
158
+ color: 'var(--fg1)',
159
+ overflow: 'hidden',
160
+ textOverflow: 'ellipsis',
161
+ whiteSpace: 'nowrap'
162
+ }
163
+ }, s.label), /*#__PURE__*/React.createElement("div", {
164
+ style: {
165
+ height: 14,
166
+ background: 'var(--bg-sunken)',
167
+ borderRadius: 3,
168
+ display: 'flex',
169
+ overflow: 'hidden'
170
+ }
171
+ }, s.segs.map((g, j) => /*#__PURE__*/React.createElement("div", {
172
+ key: j,
173
+ style: {
174
+ width: `${g.n / s.total * 100}%`,
175
+ background: `var(--status-${g.k})`
176
+ }
177
+ }))), /*#__PURE__*/React.createElement("div", {
178
+ style: {
179
+ fontFamily: 'var(--font-mono)',
180
+ fontSize: 11,
181
+ color: 'var(--fg3)',
182
+ textAlign: 'right'
183
+ }
184
+ }, s.total)))), hiddenSuiteCount > 0 && /*#__PURE__*/React.createElement("div", {
185
+ style: {
186
+ display: 'flex',
187
+ justifyContent: 'center',
188
+ marginTop: 14,
189
+ paddingTop: 12,
190
+ borderTop: '1px solid var(--line)'
191
+ }
192
+ }, /*#__PURE__*/React.createElement("button", {
193
+ className: "btn btn-ghost",
194
+ style: {
195
+ height: 28,
196
+ fontSize: 12
197
+ },
198
+ onClick: () => setShowAllSuites(true)
199
+ }, "Show ", hiddenSuiteCount, " more suites \u2192")), showAllSuites && suiteStatusBars.length > SUITE_CAP && /*#__PURE__*/React.createElement("div", {
200
+ style: {
201
+ display: 'flex',
202
+ justifyContent: 'center',
203
+ marginTop: 14,
204
+ paddingTop: 12,
205
+ borderTop: '1px solid var(--line)'
206
+ }
207
+ }, /*#__PURE__*/React.createElement("button", {
208
+ className: "btn btn-ghost",
209
+ style: {
210
+ height: 28,
211
+ fontSize: 12
212
+ },
213
+ onClick: () => setShowAllSuites(false)
214
+ }, "\u2191 Collapse to top ", SUITE_CAP))), /*#__PURE__*/React.createElement("div", {
215
+ className: "card"
216
+ }, /*#__PURE__*/React.createElement("div", {
217
+ className: "hd"
218
+ }, /*#__PURE__*/React.createElement("h3", null, "Duration distribution"), /*#__PURE__*/React.createElement("div", {
219
+ className: "meta"
220
+ }, "all ", totalTests, " tests")), /*#__PURE__*/React.createElement(DurationHistogram, {
221
+ buckets: HISTOGRAM
222
+ })), /*#__PURE__*/React.createElement("div", {
223
+ className: "card",
224
+ style: {
225
+ gridColumn: 'span 2'
226
+ }
227
+ }, /*#__PURE__*/React.createElement("div", {
228
+ className: "hd"
229
+ }, /*#__PURE__*/React.createElement("h3", null, "Flake rate vs duration"), /*#__PURE__*/React.createElement("div", {
230
+ className: "meta"
231
+ }, "last 80 runs \xB7 circle size = sample count")), hasFlake ? /*#__PURE__*/React.createElement(FlakeScatter, {
232
+ tests: Object.values(RICH_TESTS).filter(t => t.flakeRate > 0).map(t => ({
233
+ name: t.name,
234
+ runs: 80,
235
+ flakeRate: t.flakeRate,
236
+ avgDur: t.avgDurMs
237
+ }))
238
+ }) : /*#__PURE__*/React.createElement("div", {
239
+ style: {
240
+ padding: 30,
241
+ color: 'var(--fg3)',
242
+ textAlign: 'center',
243
+ fontFamily: 'var(--font-mono)',
244
+ fontSize: 12
245
+ }
246
+ }, "Flake-rate analysis requires run history. Run kensho generate over multiple runs to populate."))));
247
+ }
248
+
249
+ // ============== TIMELINE PAGE ==============
250
+ function TimelinePage() {
251
+ const TIMELINE_TESTS = window.TIMELINE_TESTS || [];
252
+ const RICH_TESTS = window.RICH_TESTS || {};
253
+ const KENSHO_INDEX = window.KENSHO_INDEX || {};
254
+ const fmt = window._kenshoFmtDuration || (ms => ms + 'ms');
255
+
256
+ // View modes — at scale (>50 tests) the Gantt becomes unreadable. Default to
257
+ // 'top' (longest 25), with toggles for failures-only and full view.
258
+ const [mode, setMode] = useStateP(TIMELINE_TESTS.length > 50 ? 'top' : 'all');
259
+ const failuresOnly = TIMELINE_TESTS.filter(t => t.status === 'failed' || t.status === 'broken');
260
+ let visibleTests;
261
+ if (mode === 'top') {
262
+ visibleTests = [...TIMELINE_TESTS].sort((a, b) => b.durMs - a.durMs).slice(0, 25);
263
+ } else if (mode === 'failures') {
264
+ visibleTests = failuresOnly;
265
+ } else {
266
+ visibleTests = TIMELINE_TESTS;
267
+ }
268
+
269
+ // Re-base start times so the Gantt fills the canvas regardless of mode —
270
+ // showing 25 longest tests with sparse start times leaves dead space at
271
+ // the front of the chart otherwise.
272
+ const minStart = visibleTests.length ? Math.min(...visibleTests.map(t => t.start)) : 0;
273
+ const rebased = visibleTests.map(t => ({
274
+ ...t,
275
+ start: t.start - minStart
276
+ }));
277
+ const totalMs = rebased.length ? Math.max(...rebased.map(t => t.start + t.durMs)) + 200 : 1000;
278
+ const fullDurationMs = TIMELINE_TESTS.length ? Math.max(...TIMELINE_TESTS.map(t => t.start + t.durMs)) : 0;
279
+ const hasHistory = (KENSHO_INDEX.history?.length || 0) > 0;
280
+ const hasRetries = Object.values(RICH_TESTS).some(t => t.retries > 0);
281
+ const runId = KENSHO_INDEX.runId ? '#' + KENSHO_INDEX.runId : '';
282
+ const workers = KENSHO_INDEX.env?.workers || 1;
283
+ const MODES = [['top', 'Top 25 longest', TIMELINE_TESTS.length > 0 ? Math.min(25, TIMELINE_TESTS.length) : 0], ['failures', 'Failures only', failuresOnly.length], ['all', 'All', TIMELINE_TESTS.length]].filter(([id, _, n]) => n > 0);
284
+ return /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement("h1", {
285
+ className: "k-h1",
286
+ style: {
287
+ marginBottom: 4
288
+ }
289
+ }, "Timeline"), /*#__PURE__*/React.createElement("div", {
290
+ className: "k-meta",
291
+ style: {
292
+ marginBottom: 18
293
+ }
294
+ }, "Run ", runId, " \xB7 ", workers, " parallel worker", workers !== 1 ? 's' : '', " \xB7 total ", fmt(fullDurationMs)), /*#__PURE__*/React.createElement("div", {
295
+ className: "card"
296
+ }, /*#__PURE__*/React.createElement("div", {
297
+ className: "hd"
298
+ }, /*#__PURE__*/React.createElement("h3", null, "Per-test execution \xB7 Gantt"), /*#__PURE__*/React.createElement("div", {
299
+ className: "meta"
300
+ }, "click any bar to open the test in detail")), /*#__PURE__*/React.createElement("div", {
301
+ style: {
302
+ display: 'flex',
303
+ gap: 6,
304
+ marginBottom: 14,
305
+ flexWrap: 'wrap'
306
+ }
307
+ }, MODES.map(([id, label, n]) => {
308
+ const active = mode === id;
309
+ return /*#__PURE__*/React.createElement("button", {
310
+ key: id,
311
+ onClick: () => setMode(id),
312
+ style: {
313
+ display: 'inline-flex',
314
+ alignItems: 'center',
315
+ gap: 6,
316
+ padding: '4px 10px',
317
+ borderRadius: 999,
318
+ border: '1px solid ' + (active ? 'var(--brand-blue-500)' : 'var(--line)'),
319
+ background: active ? 'var(--brand-blue-500)' : 'var(--bg-elev)',
320
+ color: active ? '#fff' : 'var(--fg2)',
321
+ fontFamily: 'var(--font-body)',
322
+ fontSize: 12,
323
+ fontWeight: 600,
324
+ cursor: 'pointer',
325
+ transition: 'all var(--dur-fast)'
326
+ }
327
+ }, label, /*#__PURE__*/React.createElement("span", {
328
+ style: {
329
+ fontFamily: 'var(--font-mono)',
330
+ fontSize: 11,
331
+ opacity: 0.9
332
+ }
333
+ }, n));
334
+ })), rebased.length > 0 ? /*#__PURE__*/React.createElement(TimelineGantt, {
335
+ tests: rebased,
336
+ totalMs: totalMs,
337
+ onOpen: t => {
338
+ if (t.id && RICH_TESTS[t.id]) {
339
+ window.__openTest?.(t.id);
340
+ return;
341
+ }
342
+ const match = Object.values(RICH_TESTS).find(r => r.name === t.name);
343
+ if (match) window.__openTest?.(match.id);
344
+ }
345
+ }) : /*#__PURE__*/React.createElement("div", {
346
+ style: {
347
+ padding: 30,
348
+ color: 'var(--fg3)',
349
+ textAlign: 'center',
350
+ fontFamily: 'var(--font-mono)',
351
+ fontSize: 12
352
+ }
353
+ }, mode === 'failures' ? 'No failing tests in this run.' : 'No tests with timing data in this run.')), /*#__PURE__*/React.createElement("div", {
354
+ className: "card",
355
+ style: {
356
+ marginTop: 16
357
+ }
358
+ }, /*#__PURE__*/React.createElement("div", {
359
+ className: "hd"
360
+ }, /*#__PURE__*/React.createElement("h3", null, "Suite execution heatmap"), /*#__PURE__*/React.createElement("div", {
361
+ className: "meta"
362
+ }, "last 8 runs")), hasHistory ? /*#__PURE__*/React.createElement("div", {
363
+ style: {
364
+ padding: 30,
365
+ color: 'var(--fg3)',
366
+ textAlign: 'center',
367
+ fontFamily: 'var(--font-mono)',
368
+ fontSize: 12
369
+ }
370
+ }, "Heatmap view coming soon. History is populated.") : /*#__PURE__*/React.createElement("div", {
371
+ style: {
372
+ padding: 30,
373
+ color: 'var(--fg3)',
374
+ textAlign: 'center',
375
+ fontFamily: 'var(--font-mono)',
376
+ fontSize: 12
377
+ }
378
+ }, "Run history not yet populated.")), /*#__PURE__*/React.createElement("div", {
379
+ className: "card",
380
+ style: {
381
+ marginTop: 16
382
+ }
383
+ }, /*#__PURE__*/React.createElement("div", {
384
+ className: "hd"
385
+ }, /*#__PURE__*/React.createElement("h3", null, "Retry waterfall"), /*#__PURE__*/React.createElement("div", {
386
+ className: "meta"
387
+ }, "attempt-by-attempt status & duration")), hasRetries ? /*#__PURE__*/React.createElement("div", {
388
+ style: {
389
+ padding: 30,
390
+ color: 'var(--fg3)',
391
+ textAlign: 'center',
392
+ fontFamily: 'var(--font-mono)',
393
+ fontSize: 12
394
+ }
395
+ }, "Retry attempt details coming soon. Open a retried test from the suites view.") : /*#__PURE__*/React.createElement("div", {
396
+ style: {
397
+ padding: 30,
398
+ color: 'var(--fg3)',
399
+ textAlign: 'center',
400
+ fontFamily: 'var(--font-mono)',
401
+ fontSize: 12
402
+ }
403
+ }, "Run history not yet populated.")));
404
+ }
405
+
406
+ // ============== CATEGORIES PAGE — error-type classification ==============
407
+ function CategoriesPage() {
408
+ const ERROR_TYPES = window.CATEGORIES || [];
409
+ const RICH_TESTS = window.RICH_TESTS || {};
410
+ const [selectedKind, setKind] = useStateP(ERROR_TYPES[0]?.kind ?? null);
411
+ if (ERROR_TYPES.length === 0) {
412
+ return /*#__PURE__*/React.createElement("div", {
413
+ className: "card",
414
+ style: {
415
+ padding: 30,
416
+ textAlign: 'center',
417
+ color: 'var(--fg3)',
418
+ fontFamily: 'var(--font-mono)',
419
+ fontSize: 13
420
+ }
421
+ }, "No failures in this run. Nothing to categorize.");
422
+ }
423
+ const sel = ERROR_TYPES.find(e => e.kind === selectedKind) || ERROR_TYPES[0];
424
+ const totalIssues = ERROR_TYPES.reduce((a, b) => a + b.count, 0);
425
+ return /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement("div", {
426
+ style: {
427
+ display: 'flex',
428
+ alignItems: 'baseline',
429
+ justifyContent: 'space-between',
430
+ marginBottom: 14
431
+ }
432
+ }, /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement("h1", {
433
+ className: "k-h1",
434
+ style: {
435
+ marginBottom: 2
436
+ }
437
+ }, "Categories"), /*#__PURE__*/React.createElement("div", {
438
+ className: "k-meta"
439
+ }, "Failures grouped by error type \xB7 ", ERROR_TYPES.length, " types \xB7 ", totalIssues, " tests")), /*#__PURE__*/React.createElement("div", {
440
+ style: {
441
+ display: 'flex',
442
+ gap: 6
443
+ }
444
+ }, /*#__PURE__*/React.createElement("span", {
445
+ className: "badge b-failed"
446
+ }, /*#__PURE__*/React.createElement("span", {
447
+ className: "dot"
448
+ }), ERROR_TYPES.filter(e => e.family === 'failed').reduce((a, b) => a + b.count, 0), " product"), /*#__PURE__*/React.createElement("span", {
449
+ className: "badge b-broken"
450
+ }, /*#__PURE__*/React.createElement("span", {
451
+ className: "dot"
452
+ }), ERROR_TYPES.filter(e => e.family === 'broken').reduce((a, b) => a + b.count, 0), " test-defects"), /*#__PURE__*/React.createElement("span", {
453
+ className: "badge b-skipped"
454
+ }, /*#__PURE__*/React.createElement("span", {
455
+ className: "dot"
456
+ }), ERROR_TYPES.filter(e => e.family === 'skipped').reduce((a, b) => a + b.count, 0), " environment"))), /*#__PURE__*/React.createElement("div", {
457
+ className: "card",
458
+ style: {
459
+ marginBottom: 16
460
+ }
461
+ }, /*#__PURE__*/React.createElement("div", {
462
+ className: "hd"
463
+ }, /*#__PURE__*/React.createElement("h3", null, "Distribution by error type"), /*#__PURE__*/React.createElement("div", {
464
+ className: "meta"
465
+ }, totalIssues, " test failures")), /*#__PURE__*/React.createElement("div", {
466
+ style: {
467
+ display: 'flex',
468
+ flexDirection: 'column',
469
+ gap: 8
470
+ }
471
+ }, ERROR_TYPES.map(e => /*#__PURE__*/React.createElement("div", {
472
+ key: e.kind,
473
+ style: {
474
+ display: 'grid',
475
+ gridTemplateColumns: 'minmax(220px, 280px) 1fr 36px',
476
+ alignItems: 'center',
477
+ gap: 10
478
+ }
479
+ }, /*#__PURE__*/React.createElement("div", {
480
+ style: {
481
+ display: 'flex',
482
+ alignItems: 'center',
483
+ gap: 6,
484
+ fontFamily: 'var(--font-mono)',
485
+ fontSize: 12.5,
486
+ color: 'var(--fg1)'
487
+ }
488
+ }, /*#__PURE__*/React.createElement("span", {
489
+ style: {
490
+ width: 8,
491
+ height: 8,
492
+ borderRadius: 2,
493
+ background: e.color,
494
+ flexShrink: 0
495
+ }
496
+ }), e.kind), /*#__PURE__*/React.createElement("div", {
497
+ style: {
498
+ height: 18,
499
+ background: 'var(--bg-sunken)',
500
+ borderRadius: 3,
501
+ position: 'relative',
502
+ overflow: 'hidden'
503
+ }
504
+ }, /*#__PURE__*/React.createElement("div", {
505
+ style: {
506
+ width: `${e.count / totalIssues * 100}%`,
507
+ height: '100%',
508
+ background: e.color
509
+ }
510
+ })), /*#__PURE__*/React.createElement("div", {
511
+ style: {
512
+ fontFamily: 'var(--font-mono)',
513
+ fontSize: 12,
514
+ color: 'var(--fg2)',
515
+ textAlign: 'right',
516
+ fontVariantNumeric: 'tabular-nums'
517
+ }
518
+ }, e.count))))), /*#__PURE__*/React.createElement("div", {
519
+ style: {
520
+ display: 'grid',
521
+ gridTemplateColumns: 'minmax(260px, 320px) 1fr',
522
+ gap: 0,
523
+ background: 'var(--bg-elev)',
524
+ border: '1px solid var(--line)',
525
+ borderRadius: 12,
526
+ overflow: 'hidden',
527
+ minHeight: 480
528
+ }
529
+ }, /*#__PURE__*/React.createElement("div", {
530
+ style: {
531
+ borderRight: '1px solid var(--line)'
532
+ }
533
+ }, ERROR_TYPES.map(e => /*#__PURE__*/React.createElement("div", {
534
+ key: e.kind,
535
+ onClick: () => setKind(e.kind),
536
+ style: {
537
+ display: 'grid',
538
+ gridTemplateColumns: '1fr auto',
539
+ alignItems: 'center',
540
+ gap: 10,
541
+ padding: '12px 14px',
542
+ cursor: 'pointer',
543
+ borderLeft: selectedKind === e.kind ? `2px solid ${e.color}` : '2px solid transparent',
544
+ background: selectedKind === e.kind ? 'var(--accent-soft)' : 'transparent',
545
+ borderBottom: '1px solid var(--line)'
546
+ }
547
+ }, /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement("div", {
548
+ style: {
549
+ display: 'flex',
550
+ alignItems: 'center',
551
+ gap: 6,
552
+ fontFamily: 'var(--font-mono)',
553
+ fontSize: 12.5,
554
+ fontWeight: 600,
555
+ color: 'var(--fg1)'
556
+ }
557
+ }, /*#__PURE__*/React.createElement("span", {
558
+ style: {
559
+ width: 8,
560
+ height: 8,
561
+ borderRadius: 2,
562
+ background: e.color,
563
+ flexShrink: 0
564
+ }
565
+ }), e.kind), /*#__PURE__*/React.createElement("div", {
566
+ style: {
567
+ fontSize: 11,
568
+ color: 'var(--fg3)',
569
+ marginTop: 3,
570
+ textTransform: 'uppercase',
571
+ letterSpacing: '.08em'
572
+ }
573
+ }, e.family)), /*#__PURE__*/React.createElement("span", {
574
+ style: {
575
+ background: e.color,
576
+ color: '#fff',
577
+ fontSize: 11,
578
+ fontWeight: 700,
579
+ padding: '2px 8px',
580
+ borderRadius: 3,
581
+ fontFamily: 'var(--font-mono)'
582
+ }
583
+ }, e.count)))), /*#__PURE__*/React.createElement("div", {
584
+ style: {
585
+ padding: 24
586
+ }
587
+ }, sel && /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("div", {
588
+ className: "k-overline",
589
+ style: {
590
+ marginBottom: 6
591
+ }
592
+ }, sel.family, " \xB7 error type"), /*#__PURE__*/React.createElement("h2", {
593
+ className: "k-h2",
594
+ style: {
595
+ fontSize: 22,
596
+ fontFamily: 'var(--font-mono)',
597
+ fontWeight: 600,
598
+ marginBottom: 8
599
+ }
600
+ }, sel.kind), /*#__PURE__*/React.createElement("p", {
601
+ className: "k-body",
602
+ style: {
603
+ marginBottom: 18
604
+ }
605
+ }, sel.description), /*#__PURE__*/React.createElement("div", {
606
+ className: "k-overline",
607
+ style: {
608
+ marginBottom: 8
609
+ }
610
+ }, "Affected tests \xB7 ", sel.count), /*#__PURE__*/React.createElement("div", {
611
+ style: {
612
+ border: '1px solid var(--line)',
613
+ borderRadius: 8,
614
+ overflow: 'hidden'
615
+ }
616
+ }, sel.tests.map((tid, i) => {
617
+ const t = RICH_TESTS[tid];
618
+ const name = t ? t.name : tid;
619
+ const status = t ? t.status : sel.family;
620
+ const file = t ? t.file : '';
621
+ const msg = t?.error?.message || '';
622
+ const canOpen = !!t;
623
+ return /*#__PURE__*/React.createElement("div", {
624
+ key: i,
625
+ onClick: canOpen ? () => window.__openTest?.(t.id) : undefined,
626
+ title: canOpen ? `Open ${name} →` : 'Test not found in this run',
627
+ style: {
628
+ display: 'grid',
629
+ gridTemplateColumns: '24px 1fr auto',
630
+ gap: 10,
631
+ padding: '12px 14px',
632
+ borderTop: i ? '1px solid var(--line)' : 'none',
633
+ alignItems: 'flex-start',
634
+ cursor: canOpen ? 'pointer' : 'default',
635
+ transition: 'background 120ms'
636
+ },
637
+ onMouseEnter: canOpen ? e => e.currentTarget.style.background = 'var(--bg-hover)' : undefined,
638
+ onMouseLeave: canOpen ? e => e.currentTarget.style.background = 'transparent' : undefined
639
+ }, /*#__PURE__*/React.createElement("span", {
640
+ className: `s-icon ${status}`,
641
+ style: {
642
+ marginTop: 2
643
+ }
644
+ }, status === 'passed' ? '✓' : status === 'failed' ? '✕' : status === 'broken' ? '!' : '⊘'), /*#__PURE__*/React.createElement("div", {
645
+ style: {
646
+ minWidth: 0
647
+ }
648
+ }, /*#__PURE__*/React.createElement("div", {
649
+ style: {
650
+ fontFamily: 'var(--font-body)',
651
+ fontSize: 13,
652
+ fontWeight: 600,
653
+ color: 'var(--fg1)'
654
+ }
655
+ }, name), file && /*#__PURE__*/React.createElement("div", {
656
+ style: {
657
+ fontFamily: 'var(--font-mono)',
658
+ fontSize: 11,
659
+ color: 'var(--fg3)',
660
+ marginTop: 2
661
+ }
662
+ }, file), msg && /*#__PURE__*/React.createElement("div", {
663
+ style: {
664
+ fontFamily: 'var(--font-mono)',
665
+ fontSize: 11.5,
666
+ color: 'var(--status-failed-fg)',
667
+ marginTop: 6,
668
+ padding: '6px 8px',
669
+ background: 'var(--status-failed-bg)',
670
+ border: '1px solid var(--status-failed-border)',
671
+ borderRadius: 4
672
+ }
673
+ }, msg)), canOpen && /*#__PURE__*/React.createElement("span", {
674
+ style: {
675
+ alignSelf: 'center',
676
+ fontFamily: 'var(--font-mono)',
677
+ fontSize: 11,
678
+ color: 'var(--fg3)',
679
+ display: 'inline-flex',
680
+ alignItems: 'center',
681
+ gap: 4,
682
+ whiteSpace: 'nowrap'
683
+ }
684
+ }, "Open ", /*#__PURE__*/React.createElement("span", {
685
+ style: {
686
+ fontSize: 13
687
+ }
688
+ }, "\u2192")));
689
+ }))))));
690
+ }
691
+
692
+ // ============== FLAKY PAGE ==============
693
+ //
694
+ // Single-run flake derivation — Kensho is the OSS, single-run report; rolling
695
+ // flake-rate analysis lives in Kaizen (the SaaS platform). What we CAN derive
696
+ // from one run is meaningful enough to act on:
697
+ //
698
+ // · "Recovered" — passed on retry (real flake — non-deterministic test)
699
+ // · "Broken" — status=broken (test infra failed mid-execution)
700
+ // · "Failed retry" — failed even after one or more retries (flaky AND broken)
701
+ //
702
+ // Each card explains the signal in plain language so users understand the
703
+ // difference between a flake and a regular failure. Severity ordering:
704
+ // recovered > broken > failed-with-retry, since recoveries are pure flakes.
705
+ function FlakyPage() {
706
+ const RICH_TESTS = window.RICH_TESTS || {};
707
+ const allTests = Object.values(RICH_TESTS);
708
+ const fmt = window._kenshoFmtDuration || (ms => ms + 'ms');
709
+
710
+ // Bucket every test into one of three flake categories (or none).
711
+ const buckets = {
712
+ recovered: [],
713
+ broken: [],
714
+ failedWithRetries: []
715
+ };
716
+ for (const t of allTests) {
717
+ if (t.retries > 0 && t.status === 'passed') buckets.recovered.push(t);else if (t.status === 'broken') buckets.broken.push(t);else if (t.retries > 0 && (t.status === 'failed' || t.status === 'broken')) buckets.failedWithRetries.push(t);
718
+ }
719
+ const flakeTotal = buckets.recovered.length + buckets.broken.length + buckets.failedWithRetries.length;
720
+ const [filter, setFilter] = useStateP('all');
721
+
722
+ // Stable order for rendering — recoveries first (highest signal-to-noise),
723
+ // then broken, then failed-with-retries. Within each bucket, sort by retry
724
+ // count desc so the highest-friction tests bubble up.
725
+ const ALL_FLAKY = [...buckets.recovered.map(t => ({
726
+ ...t,
727
+ _bucket: 'recovered'
728
+ })), ...buckets.broken.map(t => ({
729
+ ...t,
730
+ _bucket: 'broken'
731
+ })), ...buckets.failedWithRetries.map(t => ({
732
+ ...t,
733
+ _bucket: 'failedWithRetries'
734
+ }))].sort((a, b) => (b.retries || 0) - (a.retries || 0));
735
+ const visible = filter === 'all' ? ALL_FLAKY : ALL_FLAKY.filter(t => t._bucket === filter);
736
+
737
+ // j/k/Enter shortcuts on the Flaky list — keep selection local; pressing
738
+ // Enter delegates to the global window.__openTest hook from app.jsx.
739
+ const [selectedIdx, setSelectedIdx] = useStateP(-1);
740
+ React.useEffect(() => {
741
+ const onMove = e => {
742
+ if (visible.length === 0) return;
743
+ const delta = e.detail?.delta || 0;
744
+ setSelectedIdx(prev => {
745
+ const idx = prev === -1 ? delta > 0 ? 0 : visible.length - 1 : prev + delta;
746
+ return Math.max(0, Math.min(visible.length - 1, idx));
747
+ });
748
+ };
749
+ const onOpen = () => {
750
+ if (selectedIdx >= 0 && visible[selectedIdx]) window.__openTest?.(visible[selectedIdx].id);
751
+ };
752
+ window.addEventListener('kensho:move-selection', onMove);
753
+ window.addEventListener('kensho:open-selection', onOpen);
754
+ return () => {
755
+ window.removeEventListener('kensho:move-selection', onMove);
756
+ window.removeEventListener('kensho:open-selection', onOpen);
757
+ };
758
+ }, [selectedIdx, visible]);
759
+ const passRate = allTests.length > 0 ? Math.round((allTests.length - flakeTotal) / allTests.length * 100) : 100;
760
+ const flakeRate = allTests.length > 0 ? (flakeTotal / allTests.length * 100).toFixed(1) : '0';
761
+ if (allTests.length === 0) {
762
+ return /*#__PURE__*/React.createElement("div", {
763
+ className: "card",
764
+ style: {
765
+ padding: 30,
766
+ textAlign: 'center',
767
+ color: 'var(--fg3)'
768
+ }
769
+ }, "No tests in this report.");
770
+ }
771
+ if (flakeTotal === 0) {
772
+ return /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement("h1", {
773
+ className: "k-h1",
774
+ style: {
775
+ marginBottom: 4
776
+ }
777
+ }, "Flaky tests"), /*#__PURE__*/React.createElement("div", {
778
+ className: "k-meta",
779
+ style: {
780
+ marginBottom: 24
781
+ }
782
+ }, "Tests that retried, recovered, or broke during execution"), /*#__PURE__*/React.createElement("div", {
783
+ className: "card",
784
+ style: {
785
+ padding: '48px 32px',
786
+ textAlign: 'center',
787
+ background: 'linear-gradient(180deg, var(--status-passed-bg), transparent)'
788
+ }
789
+ }, /*#__PURE__*/React.createElement("div", {
790
+ style: {
791
+ width: 64,
792
+ height: 64,
793
+ borderRadius: 999,
794
+ background: 'var(--status-passed-bg)',
795
+ border: '2px solid var(--status-passed-border)',
796
+ display: 'inline-flex',
797
+ alignItems: 'center',
798
+ justifyContent: 'center',
799
+ marginBottom: 16
800
+ }
801
+ }, /*#__PURE__*/React.createElement("span", {
802
+ style: {
803
+ fontSize: 30,
804
+ color: 'var(--status-passed)'
805
+ }
806
+ }, "\u2713")), /*#__PURE__*/React.createElement("h2", {
807
+ className: "k-h2",
808
+ style: {
809
+ marginBottom: 8,
810
+ fontSize: 22
811
+ }
812
+ }, "No flaky tests detected"), /*#__PURE__*/React.createElement("p", {
813
+ className: "k-body",
814
+ style: {
815
+ maxWidth: 460,
816
+ margin: '0 auto',
817
+ color: 'var(--fg2)'
818
+ }
819
+ }, "Every test ran cleanly on the first attempt \u2014 no retries, no broken executions. Flaky-test detection here is single-run; rolling flake-rate analysis (last N runs) lives in the Kaizen platform.")));
820
+ }
821
+ const FILTERS = [['all', 'All flaky', flakeTotal], ['recovered', 'Recovered', buckets.recovered.length], ['broken', 'Broken', buckets.broken.length], ['failedWithRetries', 'Failed retry', buckets.failedWithRetries.length]].filter(([id, _, n]) => id === 'all' || n > 0);
822
+ return /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement("div", {
823
+ style: {
824
+ display: 'flex',
825
+ alignItems: 'baseline',
826
+ justifyContent: 'space-between',
827
+ marginBottom: 14,
828
+ flexWrap: 'wrap',
829
+ gap: 16
830
+ }
831
+ }, /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement("h1", {
832
+ className: "k-h1",
833
+ style: {
834
+ marginBottom: 4
835
+ }
836
+ }, "Flaky tests"), /*#__PURE__*/React.createElement("div", {
837
+ className: "k-meta"
838
+ }, flakeTotal, " of ", allTests.length, " tests showed instability \xB7 ", flakeRate, "% flake rate")), /*#__PURE__*/React.createElement("div", {
839
+ style: {
840
+ fontFamily: 'var(--font-mono)',
841
+ fontSize: 11,
842
+ color: 'var(--fg3)',
843
+ maxWidth: 380,
844
+ textAlign: 'right',
845
+ lineHeight: 1.55
846
+ }
847
+ }, "Single-run signal \u2014 derived from ", /*#__PURE__*/React.createElement("code", {
848
+ style: {
849
+ color: 'var(--fg2)'
850
+ }
851
+ }, "retries"), " + ", /*#__PURE__*/React.createElement("code", {
852
+ style: {
853
+ color: 'var(--fg2)'
854
+ }
855
+ }, "broken"), ". For rolling flake-rate over many runs, use the Kaizen platform.")), /*#__PURE__*/React.createElement("div", {
856
+ style: {
857
+ display: 'grid',
858
+ gridTemplateColumns: 'repeat(3, 1fr)',
859
+ gap: 16,
860
+ marginBottom: 18
861
+ }
862
+ }, /*#__PURE__*/React.createElement(FlakyStatCard, {
863
+ accent: "var(--status-broken)",
864
+ icon: "rotate-ccw",
865
+ label: "Recovered",
866
+ value: buckets.recovered.length,
867
+ desc: "Passed on retry \u2014 a real flake (non-deterministic). Investigate the test for hidden timing or state assumptions.",
868
+ onClick: buckets.recovered.length > 0 ? () => setFilter('recovered') : null
869
+ }), /*#__PURE__*/React.createElement(FlakyStatCard, {
870
+ accent: "#7C5CFF",
871
+ icon: "zap-off",
872
+ label: "Broken",
873
+ value: buckets.broken.length,
874
+ desc: "Test infrastructure failed mid-execution (setup, teardown, or fixture). Often unrelated to product behavior.",
875
+ onClick: buckets.broken.length > 0 ? () => setFilter('broken') : null
876
+ }), /*#__PURE__*/React.createElement(FlakyStatCard, {
877
+ accent: "var(--status-failed)",
878
+ icon: "alert-triangle",
879
+ label: "Failed retry",
880
+ value: buckets.failedWithRetries.length,
881
+ desc: "Failed even after one or more retries. Could be a real defect masquerading as a flake \u2014 prioritize.",
882
+ onClick: buckets.failedWithRetries.length > 0 ? () => setFilter('failedWithRetries') : null
883
+ })), /*#__PURE__*/React.createElement("div", {
884
+ className: "card",
885
+ style: {
886
+ marginBottom: 16
887
+ }
888
+ }, /*#__PURE__*/React.createElement("div", {
889
+ className: "hd"
890
+ }, /*#__PURE__*/React.createElement("h3", null, "Affected tests"), /*#__PURE__*/React.createElement("div", {
891
+ className: "meta"
892
+ }, visible.length, " shown \xB7 sorted by retry count")), /*#__PURE__*/React.createElement("div", {
893
+ style: {
894
+ display: 'flex',
895
+ gap: 6,
896
+ padding: '2px 0 14px',
897
+ flexWrap: 'wrap'
898
+ }
899
+ }, FILTERS.map(([id, label, n]) => {
900
+ const active = filter === id;
901
+ return /*#__PURE__*/React.createElement("button", {
902
+ key: id,
903
+ onClick: () => setFilter(id),
904
+ style: {
905
+ display: 'inline-flex',
906
+ alignItems: 'center',
907
+ gap: 6,
908
+ padding: '4px 10px',
909
+ borderRadius: 999,
910
+ border: '1px solid ' + (active ? 'var(--brand-blue-500)' : 'var(--line)'),
911
+ background: active ? 'var(--brand-blue-500)' : 'var(--bg-elev)',
912
+ color: active ? '#fff' : 'var(--fg2)',
913
+ fontFamily: 'var(--font-body)',
914
+ fontSize: 12,
915
+ fontWeight: 600,
916
+ cursor: 'pointer',
917
+ transition: 'all var(--dur-fast)'
918
+ }
919
+ }, label, /*#__PURE__*/React.createElement("span", {
920
+ style: {
921
+ fontFamily: 'var(--font-mono)',
922
+ fontSize: 11,
923
+ opacity: 0.9
924
+ }
925
+ }, n));
926
+ })), visible.length === 0 ? /*#__PURE__*/React.createElement("div", {
927
+ style: {
928
+ padding: '30px 0',
929
+ textAlign: 'center',
930
+ color: 'var(--fg3)',
931
+ fontFamily: 'var(--font-mono)',
932
+ fontSize: 12
933
+ }
934
+ }, "No tests in this category.") : /*#__PURE__*/React.createElement("div", {
935
+ style: {
936
+ display: 'flex',
937
+ flexDirection: 'column'
938
+ }
939
+ }, visible.map((t, i) => /*#__PURE__*/React.createElement(FlakyTestRow, {
940
+ key: t.id,
941
+ test: t,
942
+ index: i,
943
+ selected: i === selectedIdx
944
+ })))));
945
+ }
946
+
947
+ // FlakyStatCard — stat banner card. Subtle gradient + accent stripe.
948
+ function FlakyStatCard({
949
+ accent,
950
+ icon,
951
+ label,
952
+ value,
953
+ desc,
954
+ onClick
955
+ }) {
956
+ const clickable = !!onClick;
957
+ return /*#__PURE__*/React.createElement("div", {
958
+ onClick: onClick || undefined,
959
+ style: {
960
+ position: 'relative',
961
+ padding: '18px 20px',
962
+ background: 'var(--bg-elev)',
963
+ border: '1px solid var(--line)',
964
+ borderRadius: 12,
965
+ cursor: clickable ? 'pointer' : 'default',
966
+ overflow: 'hidden',
967
+ transition: 'transform var(--dur-fast), border-color var(--dur-fast), background var(--dur-fast)'
968
+ },
969
+ onMouseEnter: clickable ? e => {
970
+ e.currentTarget.style.borderColor = accent;
971
+ e.currentTarget.style.transform = 'translateY(-1px)';
972
+ } : undefined,
973
+ onMouseLeave: clickable ? e => {
974
+ e.currentTarget.style.borderColor = 'var(--line)';
975
+ e.currentTarget.style.transform = 'translateY(0)';
976
+ } : undefined
977
+ }, /*#__PURE__*/React.createElement("div", {
978
+ style: {
979
+ position: 'absolute',
980
+ left: 0,
981
+ top: 0,
982
+ bottom: 0,
983
+ width: 4,
984
+ background: accent
985
+ }
986
+ }), /*#__PURE__*/React.createElement("div", {
987
+ style: {
988
+ position: 'absolute',
989
+ right: -30,
990
+ top: -30,
991
+ width: 120,
992
+ height: 120,
993
+ borderRadius: '50%',
994
+ background: `radial-gradient(circle, ${accent}22 0%, transparent 70%)`
995
+ }
996
+ }), /*#__PURE__*/React.createElement("div", {
997
+ style: {
998
+ display: 'flex',
999
+ alignItems: 'center',
1000
+ gap: 8,
1001
+ marginBottom: 8,
1002
+ position: 'relative'
1003
+ }
1004
+ }, /*#__PURE__*/React.createElement("span", {
1005
+ style: {
1006
+ width: 28,
1007
+ height: 28,
1008
+ borderRadius: 8,
1009
+ background: `${accent}1F`,
1010
+ color: accent,
1011
+ display: 'inline-flex',
1012
+ alignItems: 'center',
1013
+ justifyContent: 'center'
1014
+ }
1015
+ }, /*#__PURE__*/React.createElement(Icon, {
1016
+ name: icon,
1017
+ size: 14
1018
+ })), /*#__PURE__*/React.createElement("span", {
1019
+ style: {
1020
+ fontFamily: 'var(--font-mono)',
1021
+ fontSize: 10.5,
1022
+ color: 'var(--fg3)',
1023
+ letterSpacing: '.14em',
1024
+ textTransform: 'uppercase'
1025
+ }
1026
+ }, label)), /*#__PURE__*/React.createElement("div", {
1027
+ style: {
1028
+ fontFamily: 'var(--font-display)',
1029
+ fontSize: 42,
1030
+ fontWeight: 700,
1031
+ letterSpacing: -0.8,
1032
+ color: 'var(--fg1)',
1033
+ lineHeight: 1,
1034
+ marginBottom: 10
1035
+ }
1036
+ }, value), /*#__PURE__*/React.createElement("div", {
1037
+ style: {
1038
+ fontFamily: 'var(--font-body)',
1039
+ fontSize: 12.5,
1040
+ color: 'var(--fg2)',
1041
+ lineHeight: 1.55
1042
+ }
1043
+ }, desc));
1044
+ }
1045
+
1046
+ // FlakyTestRow — single affected test in the list.
1047
+ function FlakyTestRow({
1048
+ test,
1049
+ index,
1050
+ selected
1051
+ }) {
1052
+ const BUCKET_META = {
1053
+ recovered: {
1054
+ color: 'var(--status-broken)',
1055
+ label: 'RECOVERED',
1056
+ pillBg: 'var(--status-broken-bg)',
1057
+ pillFg: 'var(--status-broken-fg)',
1058
+ icon: 'rotate-ccw'
1059
+ },
1060
+ broken: {
1061
+ color: '#7C5CFF',
1062
+ label: 'BROKEN',
1063
+ pillBg: 'rgba(124,92,255,0.15)',
1064
+ pillFg: '#B69CFF',
1065
+ icon: 'zap-off'
1066
+ },
1067
+ failedWithRetries: {
1068
+ color: 'var(--status-failed)',
1069
+ label: 'FAILED RETRY',
1070
+ pillBg: 'var(--status-failed-bg)',
1071
+ pillFg: 'var(--status-failed-fg)',
1072
+ icon: 'alert-triangle'
1073
+ }
1074
+ };
1075
+ const m = BUCKET_META[test._bucket] || BUCKET_META.broken;
1076
+
1077
+ // Compute a rough "stability score" for visual weight: lower = flakier.
1078
+ // 100 base, –20 per retry, –30 if broken, –10 if failed.
1079
+ let stability = 100 - test.retries * 20;
1080
+ if (test.status === 'broken') stability -= 30;else if (test.status === 'failed') stability -= 10;
1081
+ stability = Math.max(0, Math.min(100, stability));
1082
+ const stColor = stability >= 60 ? 'var(--status-broken)' : 'var(--status-failed)';
1083
+ return /*#__PURE__*/React.createElement("div", {
1084
+ onClick: () => window.__openTest?.(test.id),
1085
+ style: {
1086
+ display: 'grid',
1087
+ gridTemplateColumns: 'auto 110px 1fr 110px auto',
1088
+ gap: 14,
1089
+ alignItems: 'center',
1090
+ padding: '12px 4px',
1091
+ cursor: 'pointer',
1092
+ borderTop: index ? '1px solid var(--line)' : 'none',
1093
+ background: selected ? 'var(--accent-soft)' : 'transparent',
1094
+ borderLeft: selected ? '2px solid var(--brand-blue-500)' : '2px solid transparent',
1095
+ transition: 'background var(--dur-fast)'
1096
+ },
1097
+ onMouseEnter: e => {
1098
+ if (!selected) e.currentTarget.style.background = 'var(--bg-hover)';
1099
+ },
1100
+ onMouseLeave: e => {
1101
+ if (!selected) e.currentTarget.style.background = 'transparent';
1102
+ }
1103
+ }, /*#__PURE__*/React.createElement("span", {
1104
+ style: {
1105
+ width: 32,
1106
+ height: 32,
1107
+ borderRadius: 8,
1108
+ background: `${m.color}1F`,
1109
+ color: m.color,
1110
+ display: 'inline-flex',
1111
+ alignItems: 'center',
1112
+ justifyContent: 'center',
1113
+ flexShrink: 0
1114
+ }
1115
+ }, /*#__PURE__*/React.createElement(Icon, {
1116
+ name: m.icon,
1117
+ size: 14
1118
+ })), /*#__PURE__*/React.createElement("span", {
1119
+ style: {
1120
+ display: 'inline-flex',
1121
+ alignItems: 'center',
1122
+ justifyContent: 'center',
1123
+ padding: '3px 0',
1124
+ background: m.pillBg,
1125
+ color: m.pillFg,
1126
+ fontFamily: 'var(--font-mono)',
1127
+ fontSize: 10.5,
1128
+ fontWeight: 700,
1129
+ letterSpacing: 0.5,
1130
+ borderRadius: 4
1131
+ }
1132
+ }, m.label), /*#__PURE__*/React.createElement("div", {
1133
+ style: {
1134
+ minWidth: 0
1135
+ }
1136
+ }, /*#__PURE__*/React.createElement("div", {
1137
+ style: {
1138
+ fontFamily: 'var(--font-body)',
1139
+ fontSize: 13.5,
1140
+ fontWeight: 600,
1141
+ color: 'var(--fg1)',
1142
+ overflow: 'hidden',
1143
+ textOverflow: 'ellipsis',
1144
+ whiteSpace: 'nowrap'
1145
+ }
1146
+ }, test.name), /*#__PURE__*/React.createElement("div", {
1147
+ style: {
1148
+ fontFamily: 'var(--font-mono)',
1149
+ fontSize: 11,
1150
+ color: 'var(--fg3)',
1151
+ marginTop: 3,
1152
+ overflow: 'hidden',
1153
+ textOverflow: 'ellipsis',
1154
+ whiteSpace: 'nowrap'
1155
+ }
1156
+ }, test.suite ? /*#__PURE__*/React.createElement("span", null, test.suite) : null, test.suite && test.file ? /*#__PURE__*/React.createElement("span", null, " \xB7 ") : null, test.file ? /*#__PURE__*/React.createElement("span", null, test.file) : null)), /*#__PURE__*/React.createElement("div", {
1157
+ style: {
1158
+ display: 'flex',
1159
+ flexDirection: 'column',
1160
+ gap: 4,
1161
+ alignItems: 'flex-end'
1162
+ }
1163
+ }, /*#__PURE__*/React.createElement("div", {
1164
+ style: {
1165
+ fontFamily: 'var(--font-mono)',
1166
+ fontSize: 11,
1167
+ color: 'var(--fg3)'
1168
+ }
1169
+ }, test.retries > 0 ? `${test.retries} ${test.retries === 1 ? 'retry' : 'retries'}` : 'no retries'), /*#__PURE__*/React.createElement("div", {
1170
+ style: {
1171
+ width: 90,
1172
+ height: 4,
1173
+ background: 'var(--bg-sunken)',
1174
+ borderRadius: 999,
1175
+ overflow: 'hidden'
1176
+ }
1177
+ }, /*#__PURE__*/React.createElement("div", {
1178
+ style: {
1179
+ width: `${stability}%`,
1180
+ height: '100%',
1181
+ background: stColor,
1182
+ transition: 'width var(--dur-fast)'
1183
+ }
1184
+ })), /*#__PURE__*/React.createElement("div", {
1185
+ style: {
1186
+ fontFamily: 'var(--font-mono)',
1187
+ fontSize: 10,
1188
+ color: 'var(--fg4)',
1189
+ letterSpacing: '.08em',
1190
+ textTransform: 'uppercase'
1191
+ }
1192
+ }, "stability ", stability, "%")), /*#__PURE__*/React.createElement("div", {
1193
+ style: {
1194
+ fontFamily: 'var(--font-mono)',
1195
+ fontSize: 12,
1196
+ color: 'var(--fg3)'
1197
+ }
1198
+ }, test.dur));
1199
+ }
1200
+
1201
+ // ============== SUITES PAGE — uses TreeDetailPage ==============
1202
+ function SuitesView({
1203
+ onOpen
1204
+ }) {
1205
+ const tree = window.SUITE_TREE || [];
1206
+ if (tree.length === 0) {
1207
+ return /*#__PURE__*/React.createElement("div", {
1208
+ className: "card",
1209
+ style: {
1210
+ padding: 30,
1211
+ textAlign: 'center',
1212
+ color: 'var(--fg3)'
1213
+ }
1214
+ }, "No suites in this report.");
1215
+ }
1216
+ return /*#__PURE__*/React.createElement(TreeDetailPage, {
1217
+ title: "Suites",
1218
+ subtitle: "Tests grouped by suite organization",
1219
+ tree: tree
1220
+ });
1221
+ }
1222
+
1223
+ // ============== PACKAGES PAGE — file-path tree, opens detail in-place ==============
1224
+ function PackagesPage() {
1225
+ const RICH_TESTS = window.RICH_TESTS || {};
1226
+ const allTests = Object.values(RICH_TESTS);
1227
+ if (allTests.length === 0) {
1228
+ return /*#__PURE__*/React.createElement("div", {
1229
+ className: "card",
1230
+ style: {
1231
+ padding: 30,
1232
+ textAlign: 'center',
1233
+ color: 'var(--fg3)'
1234
+ }
1235
+ }, "No tests in this report.");
1236
+ }
1237
+
1238
+ // Build a tree from RICH_TESTS file paths, e.g. src/test/auth/login.spec.ts → src › test › auth › login.spec.ts
1239
+ const root = {
1240
+ _children: {}
1241
+ };
1242
+ allTests.forEach(t => {
1243
+ if (!t.file) return;
1244
+ const parts = t.file.split(':')[0].split('/');
1245
+ let node = root;
1246
+ parts.forEach((p, i) => {
1247
+ if (!node._children[p]) node._children[p] = {
1248
+ _children: {},
1249
+ _name: p,
1250
+ _isFile: i === parts.length - 1
1251
+ };
1252
+ node = node._children[p];
1253
+ if (node._isFile) node._test = t.id;
1254
+ });
1255
+ });
1256
+ const toTree = (node, prefix = '') => Object.entries(node._children).map(([name, child]) => {
1257
+ const id = prefix + '/' + name;
1258
+ if (child._isFile) {
1259
+ return {
1260
+ id,
1261
+ testId: child._test
1262
+ };
1263
+ }
1264
+ return {
1265
+ id,
1266
+ name,
1267
+ children: toTree(child, id)
1268
+ };
1269
+ });
1270
+
1271
+ // Collapse single-child folders for cleaner display
1272
+ const collapse = nodes => nodes.map(n => {
1273
+ if (!n.children) return n;
1274
+ let kids = collapse(n.children);
1275
+ while (kids.length === 1 && kids[0].children) {
1276
+ n = {
1277
+ ...n,
1278
+ name: n.name + ' › ' + kids[0].name,
1279
+ children: kids[0].children,
1280
+ id: kids[0].id
1281
+ };
1282
+ kids = n.children;
1283
+ }
1284
+ return {
1285
+ ...n,
1286
+ children: kids
1287
+ };
1288
+ });
1289
+ const tree = collapse(toTree(root));
1290
+ if (tree.length === 0) {
1291
+ return /*#__PURE__*/React.createElement("div", {
1292
+ className: "card",
1293
+ style: {
1294
+ padding: 30,
1295
+ textAlign: 'center',
1296
+ color: 'var(--fg3)'
1297
+ }
1298
+ }, "No source-file metadata in this report. Add filePath to your test cases to populate this tree.");
1299
+ }
1300
+ return /*#__PURE__*/React.createElement(TreeDetailPage, {
1301
+ title: "Packages",
1302
+ subtitle: "Tests grouped by source-code package",
1303
+ tree: tree
1304
+ });
1305
+ }
1306
+
1307
+ // ============== BEHAVIORS PAGE — Epic › Feature › Story tree ==============
1308
+ function BehaviorsPage() {
1309
+ const tree = window.BEHAVIOR_TREE || [];
1310
+ if (tree.length === 0) {
1311
+ return /*#__PURE__*/React.createElement("div", {
1312
+ className: "card",
1313
+ style: {
1314
+ padding: 30,
1315
+ textAlign: 'center',
1316
+ color: 'var(--fg3)'
1317
+ }
1318
+ }, "No BDD/behavior annotations in this report. Add behavior.epic/feature/scenario to your test cases to populate this tree.");
1319
+ }
1320
+ return /*#__PURE__*/React.createElement(TreeDetailPage, {
1321
+ title: "Behaviors",
1322
+ subtitle: "BDD tree \xB7 Epic \u203A Feature \u203A Story \xB7 Given/When/Then in test details",
1323
+ tree: tree
1324
+ });
1325
+ }
1326
+
1327
+ // ============== HISTORY PAGE ==============
1328
+ function HistoryPage() {
1329
+ const HISTORY_RUNS = window.HISTORY_RUNS || [];
1330
+ const TREND_RUNS = window.TREND_RUNS || [];
1331
+ return /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement("h1", {
1332
+ className: "k-h1",
1333
+ style: {
1334
+ marginBottom: 4
1335
+ }
1336
+ }, "History"), /*#__PURE__*/React.createElement("div", {
1337
+ className: "k-meta",
1338
+ style: {
1339
+ marginBottom: 18
1340
+ }
1341
+ }, "Last ", HISTORY_RUNS.length, " run", HISTORY_RUNS.length !== 1 ? 's' : '', " \xB7 main & PR branches"), /*#__PURE__*/React.createElement("div", {
1342
+ className: "card",
1343
+ style: {
1344
+ marginBottom: 16
1345
+ }
1346
+ }, /*#__PURE__*/React.createElement("div", {
1347
+ className: "hd"
1348
+ }, /*#__PURE__*/React.createElement("h3", null, "Pass-rate trend"), /*#__PURE__*/React.createElement("div", {
1349
+ className: "meta"
1350
+ }, "last ", TREND_RUNS.length, " run", TREND_RUNS.length !== 1 ? 's' : '')), TREND_RUNS.length > 0 ? /*#__PURE__*/React.createElement(TrendChartV2, {
1351
+ runs: TREND_RUNS
1352
+ }) : /*#__PURE__*/React.createElement("div", {
1353
+ style: {
1354
+ padding: 30,
1355
+ color: 'var(--fg3)',
1356
+ textAlign: 'center',
1357
+ fontFamily: 'var(--font-mono)',
1358
+ fontSize: 12
1359
+ }
1360
+ }, "No run history yet. Run kensho generate over multiple runs to populate.")), /*#__PURE__*/React.createElement("div", {
1361
+ className: "card kv-flush"
1362
+ }, /*#__PURE__*/React.createElement("div", {
1363
+ style: {
1364
+ display: 'grid',
1365
+ gridTemplateColumns: '24px 200px 1fr 110px 110px 80px',
1366
+ gap: 12,
1367
+ padding: '10px 20px',
1368
+ background: 'var(--bg-sunken)',
1369
+ borderBottom: '1px solid var(--line)',
1370
+ fontSize: 11,
1371
+ letterSpacing: '.12em',
1372
+ textTransform: 'uppercase',
1373
+ fontWeight: 700,
1374
+ color: 'var(--fg3)'
1375
+ }
1376
+ }, /*#__PURE__*/React.createElement("div", null), /*#__PURE__*/React.createElement("div", null, "Run"), /*#__PURE__*/React.createElement("div", null, "Distribution"), /*#__PURE__*/React.createElement("div", null, "Branch \xB7 actor"), /*#__PURE__*/React.createElement("div", null, "Duration"), /*#__PURE__*/React.createElement("div", {
1377
+ style: {
1378
+ textAlign: 'right'
1379
+ }
1380
+ }, "When")), HISTORY_RUNS.map((r, i) => {
1381
+ const total = r.passed + r.failed + r.broken + r.skipped;
1382
+ return /*#__PURE__*/React.createElement("div", {
1383
+ key: i,
1384
+ style: {
1385
+ display: 'grid',
1386
+ gridTemplateColumns: '24px 200px 1fr 110px 110px 80px',
1387
+ gap: 12,
1388
+ padding: '12px 20px',
1389
+ borderBottom: i < HISTORY_RUNS.length - 1 ? '1px solid var(--line)' : 'none',
1390
+ alignItems: 'center',
1391
+ cursor: 'pointer'
1392
+ }
1393
+ }, /*#__PURE__*/React.createElement("span", {
1394
+ className: `s-icon ${r.status}`
1395
+ }, r.status === 'passed' ? '✓' : '✕'), /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement("div", {
1396
+ style: {
1397
+ fontFamily: 'var(--font-mono)',
1398
+ fontSize: 12,
1399
+ color: 'var(--fg1)'
1400
+ }
1401
+ }, r.id), /*#__PURE__*/React.createElement("div", {
1402
+ style: {
1403
+ fontFamily: 'var(--font-mono)',
1404
+ fontSize: 11,
1405
+ color: 'var(--fg3)'
1406
+ }
1407
+ }, r.passed, " passed \xB7 ", r.failed, " failed \xB7 ", r.broken, " broken \xB7 ", r.skipped, " skipped")), /*#__PURE__*/React.createElement("div", {
1408
+ style: {
1409
+ height: 12,
1410
+ background: 'var(--bg-sunken)',
1411
+ borderRadius: 3,
1412
+ display: 'flex',
1413
+ overflow: 'hidden'
1414
+ }
1415
+ }, r.passed > 0 && /*#__PURE__*/React.createElement("div", {
1416
+ style: {
1417
+ width: `${r.passed / total * 100}%`,
1418
+ background: 'var(--status-passed)'
1419
+ }
1420
+ }), r.failed > 0 && /*#__PURE__*/React.createElement("div", {
1421
+ style: {
1422
+ width: `${r.failed / total * 100}%`,
1423
+ background: 'var(--status-failed)'
1424
+ }
1425
+ }), r.broken > 0 && /*#__PURE__*/React.createElement("div", {
1426
+ style: {
1427
+ width: `${r.broken / total * 100}%`,
1428
+ background: 'var(--status-broken)'
1429
+ }
1430
+ }), r.skipped > 0 && /*#__PURE__*/React.createElement("div", {
1431
+ style: {
1432
+ width: `${r.skipped / total * 100}%`,
1433
+ background: 'var(--status-skipped)'
1434
+ }
1435
+ })), /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement("div", {
1436
+ style: {
1437
+ fontFamily: 'var(--font-mono)',
1438
+ fontSize: 12,
1439
+ color: 'var(--fg1)'
1440
+ }
1441
+ }, r.branch), /*#__PURE__*/React.createElement("div", {
1442
+ style: {
1443
+ fontFamily: 'var(--font-mono)',
1444
+ fontSize: 11,
1445
+ color: 'var(--fg3)'
1446
+ }
1447
+ }, r.actor)), /*#__PURE__*/React.createElement("div", {
1448
+ style: {
1449
+ fontFamily: 'var(--font-mono)',
1450
+ fontSize: 12,
1451
+ color: 'var(--fg2)'
1452
+ }
1453
+ }, r.dur), /*#__PURE__*/React.createElement("div", {
1454
+ style: {
1455
+ fontFamily: 'var(--font-mono)',
1456
+ fontSize: 11,
1457
+ color: 'var(--fg3)',
1458
+ textAlign: 'right'
1459
+ }
1460
+ }, r.when));
1461
+ })));
1462
+ }
1463
+ Object.assign(window, {
1464
+ GraphsPage,
1465
+ TimelinePage,
1466
+ CategoriesPage,
1467
+ BehaviorsPage,
1468
+ PackagesPage,
1469
+ HistoryPage,
1470
+ SuitesView,
1471
+ FlakyPage
1472
+ });