@linxiraos/pi-stats 1.1.7 → 1.1.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (89) hide show
  1. package/CHANGELOG.md +4 -4
  2. package/package.json +87 -94
  3. package/src/client/App.tsx +11 -1
  4. package/src/client/api.ts +23 -0
  5. package/src/client/app/routes.ts +7 -0
  6. package/src/client/data/useHashRoute.ts +28 -10
  7. package/src/client/routes/ProvidersRoute.tsx +1 -0
  8. package/src/client/routes/TracesRoute.tsx +182 -0
  9. package/src/client/routes/index.ts +1 -0
  10. package/src/client/styles.css +433 -111
  11. package/src/client/traces/AggregatesPanel.tsx +88 -0
  12. package/src/client/traces/Minimap.tsx +177 -0
  13. package/src/client/traces/SpanDrawer.tsx +309 -0
  14. package/src/client/traces/SummaryStrip.tsx +37 -0
  15. package/src/client/traces/TimelineCanvas.tsx +768 -0
  16. package/src/client/traces/TraceView.tsx +344 -0
  17. package/src/client/traces/TranscriptList.tsx +166 -0
  18. package/src/client/traces/time-scale.ts +250 -0
  19. package/src/client/traces/trace-colors.ts +77 -0
  20. package/src/db.ts +77 -0
  21. package/src/gain-aggregator.ts +1 -1
  22. package/src/index.ts +1 -6
  23. package/src/parser.ts +55 -2
  24. package/src/server.ts +69 -17
  25. package/src/shared-types.ts +138 -0
  26. package/src/trace.ts +1002 -0
  27. package/src/types.ts +45 -1
  28. package/THIRD-PARTY-NOTICES.txt +0 -22909
  29. package/dist/client/index.css +0 -1
  30. package/dist/client/index.html +0 -24
  31. package/dist/client/index.js +0 -257
  32. package/dist/client/styles.css +0 -1692
  33. package/dist/types/aggregator.d.ts +0 -103
  34. package/dist/types/client/App.d.ts +0 -1
  35. package/dist/types/client/api.d.ts +0 -22
  36. package/dist/types/client/app/AppLayout.d.ts +0 -16
  37. package/dist/types/client/app/NavRail.d.ts +0 -7
  38. package/dist/types/client/app/RangeControl.d.ts +0 -7
  39. package/dist/types/client/app/SyncButton.d.ts +0 -14
  40. package/dist/types/client/app/ThemeToggle.d.ts +0 -1
  41. package/dist/types/client/app/TopBar.d.ts +0 -15
  42. package/dist/types/client/app/routes.d.ts +0 -12
  43. package/dist/types/client/components/AgentTokenShare.d.ts +0 -5
  44. package/dist/types/client/components/chart-shared.d.ts +0 -178
  45. package/dist/types/client/components/models-table-shared.d.ts +0 -175
  46. package/dist/types/client/components/range-meta.d.ts +0 -21
  47. package/dist/types/client/data/charts.d.ts +0 -1
  48. package/dist/types/client/data/formatters.d.ts +0 -13
  49. package/dist/types/client/data/useHashRoute.d.ts +0 -8
  50. package/dist/types/client/data/useResource.d.ts +0 -13
  51. package/dist/types/client/data/view-models.d.ts +0 -75
  52. package/dist/types/client/index.d.ts +0 -2
  53. package/dist/types/client/routes/BehaviorRoute.d.ts +0 -7
  54. package/dist/types/client/routes/CostsRoute.d.ts +0 -7
  55. package/dist/types/client/routes/ErrorsRoute.d.ts +0 -8
  56. package/dist/types/client/routes/GainRoute.d.ts +0 -7
  57. package/dist/types/client/routes/ModelsRoute.d.ts +0 -7
  58. package/dist/types/client/routes/OverviewRoute.d.ts +0 -8
  59. package/dist/types/client/routes/ProjectsRoute.d.ts +0 -7
  60. package/dist/types/client/routes/ProvidersRoute.d.ts +0 -7
  61. package/dist/types/client/routes/RequestsRoute.d.ts +0 -8
  62. package/dist/types/client/routes/ToolsRoute.d.ts +0 -7
  63. package/dist/types/client/routes/index.d.ts +0 -10
  64. package/dist/types/client/types.d.ts +0 -63
  65. package/dist/types/client/ui/AsyncBoundary.d.ts +0 -12
  66. package/dist/types/client/ui/DataTable.d.ts +0 -17
  67. package/dist/types/client/ui/EmptyState.d.ts +0 -7
  68. package/dist/types/client/ui/ErrorState.d.ts +0 -6
  69. package/dist/types/client/ui/JsonBlock.d.ts +0 -7
  70. package/dist/types/client/ui/MetricCluster.d.ts +0 -5
  71. package/dist/types/client/ui/Panel.d.ts +0 -7
  72. package/dist/types/client/ui/RequestDrawer.d.ts +0 -5
  73. package/dist/types/client/ui/SegmentedControl.d.ts +0 -12
  74. package/dist/types/client/ui/Skeleton.d.ts +0 -8
  75. package/dist/types/client/ui/StatusPill.d.ts +0 -7
  76. package/dist/types/client/ui/index.d.ts +0 -11
  77. package/dist/types/client/useSystemTheme.d.ts +0 -11
  78. package/dist/types/db.d.ts +0 -160
  79. package/dist/types/embedded-client.d.ts +0 -18
  80. package/dist/types/gain-aggregator.d.ts +0 -26
  81. package/dist/types/index.d.ts +0 -17
  82. package/dist/types/parser.d.ts +0 -53
  83. package/dist/types/port-conflict.d.ts +0 -16
  84. package/dist/types/server.d.ts +0 -14
  85. package/dist/types/shared-types.d.ts +0 -421
  86. package/dist/types/sync-worker.d.ts +0 -31
  87. package/dist/types/types.d.ts +0 -164
  88. package/dist/types/usage-windows.d.ts +0 -72
  89. package/dist/types/user-metrics.d.ts +0 -81
@@ -1,46 +1,41 @@
1
1
  @import "tailwindcss/index.css";
2
2
 
3
3
  /* ==========================================================================
4
- Zeta Theme Variables & Color Scheme (aligned with Zeta Web UI globals.css)
4
+ OMP Theme Variables & Color Scheme
5
5
  ========================================================================== */
6
6
  :root {
7
7
  color-scheme: dark;
8
- /* Zeta dark theme — blue accent #60a5fa, neutral-gray surfaces.
9
- Mirrors web-ui/app/globals.css `html.dark` token set. */
10
- --bg: #1a1a1a;
11
- --bg-panel: #242424;
12
- --bg-hover: #2e2e2e;
13
- --bg-selected: #383838;
14
- --bg-subtle: rgba(255,255,255,0.04);
15
- --border: #3a3a3a;
16
- --border-strong: rgba(255,255,255,0.13);
17
- --text: #e8e8e8;
18
- --text-muted: #9ca3af;
19
- --text-dim: #6b7280;
20
- --accent: #60a5fa; /* Zeta blue */
21
- --accent-hover: #93c5fd;
22
- --accent-fg: #0f172a; /* ink on accent fills */
23
- --accent-muted: rgba(96,165,250,0.18);
24
- --link: #60a5fa; /* Zeta blue */
25
- --success: #22c55e;
26
- --danger: #ef4444;
27
- --warning: #f59e0b;
28
-
29
- /* Backward-compat aliases for existing class references */
30
- --page: var(--bg);
31
- --surface: var(--bg-panel);
32
- --surface-2: var(--bg-hover);
33
- --muted: var(--text-muted);
34
- --dim: var(--text-dim);
35
-
36
- /* Bridge view-component token vocabulary */
8
+ /* OMP brand palette — packages/collab-web/src/styles/tokens.css:
9
+ deep-purple surfaces (hue 307), pink accent #ed4abf, cyan focus ring #5ad8e6. */
10
+ --page: oklch(0.16 0.02 307);
11
+ --surface: oklch(0.19 0.022 307);
12
+ --surface-2: oklch(0.235 0.026 307);
13
+ --border: oklch(1 0 0 / 9%);
14
+ --border-strong: oklch(1 0 0 / 13%);
15
+ --text: oklch(0.92 0.01 307);
16
+ --muted: oklch(0.71 0.016 307);
17
+ --dim: oklch(0.53 0.018 307);
18
+ --accent: oklch(0.674 0.23 341); /* brand pink #ed4abf */
19
+ --accent-fg: oklch(0.2 0.07 341); /* ink on accent fills */
20
+ --accent-muted: oklch(0.674 0.23 341 / 18%);
21
+ --link: oklch(0.817 0.112 205); /* brand cyan #5ad8e6 */
22
+ --success: oklch(0.76 0.14 150);
23
+ --danger: oklch(0.66 0.19 25);
24
+ --warning: oklch(0.79 0.14 85);
25
+
26
+ /* Bridge the view components' token vocabulary to the OMP base tokens so
27
+ muted/secondary text and semantic accents resolve instead of silently
28
+ falling back to inherited text color. Re-resolve per theme via var(). */
37
29
  --text-primary: var(--text);
38
- --text-secondary: var(--text-muted);
30
+ --text-secondary: var(--muted);
31
+ --text-muted: var(--dim);
39
32
  --border-subtle: var(--border);
40
33
  --accent-red: var(--danger);
41
34
  --accent-green: var(--success);
42
35
  --accent-cyan: var(--link);
43
- --bg-elevated: var(--bg-hover);
36
+ --bg-hover: var(--surface-2);
37
+ --bg-elevated: var(--surface-2);
38
+ --select-chevron: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%23999' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
44
39
 
45
40
  --radius-sm: 3px;
46
41
  --radius-md: 6px;
@@ -52,89 +47,44 @@
52
47
  @media (prefers-color-scheme: light) {
53
48
  :root:not([data-theme]) {
54
49
  color-scheme: light;
55
- --bg: #ffffff;
56
- --bg-panel: #f5f5f5;
57
- --bg-hover: #eeeeee;
58
- --bg-selected: #e8e8e8;
59
- --bg-subtle: rgba(0,0,0,0.03);
60
- --border: #e0e0e0;
61
- --border-strong: rgba(0,0,0,0.15);
62
- --text: #1a1a1a;
63
- --text-muted: #6b7280;
64
- --text-dim: #9ca3af;
65
- --accent: #2563eb; /* Zeta blue */
66
- --accent-hover: #1d4ed8;
67
- --accent-fg: #ffffff;
68
- --accent-muted: rgba(37,99,235,0.14);
69
- --link: #2563eb;
70
- --success: #16a34a;
71
- --danger: #dc2626;
72
- --warning: #d97706;
73
- /* Backward-compat aliases */
74
- --page: var(--bg);
75
- --surface: var(--bg-panel);
76
- --surface-2: var(--bg-hover);
77
- --muted: var(--text-muted);
78
- --dim: var(--text-dim);
50
+ --page: oklch(0.985 0.004 307);
51
+ --surface: oklch(1 0 0);
52
+ --surface-2: oklch(0.965 0.006 307);
53
+ --border: oklch(0 0 0 / 10%);
54
+ --border-strong: oklch(0 0 0 / 15%);
55
+ --text: oklch(0.26 0.03 307);
56
+ --muted: oklch(0.46 0.03 307);
57
+ --dim: oklch(0.58 0.025 307);
58
+ --accent: oklch(0.62 0.23 341);
59
+ --accent-fg: oklch(0.99 0.01 341);
60
+ --accent-muted: oklch(0.62 0.23 341 / 14%);
61
+ --link: oklch(0.58 0.13 230);
62
+ --success: oklch(0.55 0.13 150);
63
+ --danger: oklch(0.55 0.19 25);
64
+ --select-chevron: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%23555' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
65
+ --warning: oklch(0.6 0.13 85);
79
66
  }
80
67
  }
81
68
 
82
69
  /* Explicit theme override (toggle or persisted preference). */
83
70
  [data-theme="light"] {
84
71
  color-scheme: light;
85
- --bg: #ffffff;
86
- --bg-panel: #f5f5f5;
87
- --bg-hover: #eeeeee;
88
- --bg-selected: #e8e8e8;
89
- --bg-subtle: rgba(0,0,0,0.03);
90
- --border: #e0e0e0;
91
- --border-strong: rgba(0,0,0,0.15);
92
- --text: #1a1a1a;
93
- --text-muted: #6b7280;
94
- --text-dim: #9ca3af;
95
- --accent: #2563eb; /* Zeta blue */
96
- --accent-hover: #1d4ed8;
97
- --accent-fg: #ffffff;
98
- --accent-muted: rgba(37,99,235,0.14);
99
- --link: #2563eb;
100
- --success: #16a34a;
101
- --danger: #dc2626;
102
- --warning: #d97706;
103
- /* Backward-compat aliases */
104
- --page: var(--bg);
105
- --surface: var(--bg-panel);
106
- --surface-2: var(--bg-hover);
107
- --muted: var(--text-muted);
108
- --dim: var(--text-dim);
109
- }
110
-
111
- /* Zeta Starfield theme — NASA-punk deep-space HUD, aligned with web-ui globals.css */
112
- [data-theme="starfield"] {
113
- color-scheme: dark;
114
- --bg: #08090d;
115
- --bg-panel: #0d1117;
116
- --bg-hover: #161d2e;
117
- --bg-selected: #1a2540;
118
- --bg-subtle: rgba(217,155,38,0.05);
119
- --border: #1e2d45;
120
- --border-strong: rgba(217,155,38,0.2);
121
- --text: #e8eeff;
122
- --text-muted: #8fa3c0;
123
- --text-dim: #3d5070;
124
- --accent: #d99b26; /* Zeta Starfield gold */
125
- --accent-hover: #f0b840;
126
- --accent-fg: #0f172a;
127
- --accent-muted: rgba(217,155,38,0.18);
128
- --link: #d99b26;
129
- --success: #22c55e;
130
- --danger: #c81d25;
131
- --warning: #e05a2b;
132
- /* Backward-compat aliases */
133
- --page: var(--bg);
134
- --surface: var(--bg-panel);
135
- --surface-2: var(--bg-hover);
136
- --muted: var(--text-muted);
137
- --dim: var(--text-dim);
72
+ --page: oklch(0.985 0.004 307);
73
+ --surface: oklch(1 0 0);
74
+ --surface-2: oklch(0.965 0.006 307);
75
+ --border: oklch(0 0 0 / 10%);
76
+ --border-strong: oklch(0 0 0 / 15%);
77
+ --text: oklch(0.26 0.03 307);
78
+ --muted: oklch(0.46 0.03 307);
79
+ --dim: oklch(0.58 0.025 307);
80
+ --accent: oklch(0.62 0.23 341);
81
+ --accent-fg: oklch(0.99 0.01 341);
82
+ --accent-muted: oklch(0.62 0.23 341 / 14%);
83
+ --link: oklch(0.58 0.13 230);
84
+ --success: oklch(0.55 0.13 150);
85
+ --danger: oklch(0.55 0.19 25);
86
+ --select-chevron: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%23555' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
87
+ --warning: oklch(0.6 0.13 85);
138
88
  }
139
89
 
140
90
  /* ==========================================================================
@@ -517,6 +467,62 @@
517
467
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.18);
518
468
  }
519
469
 
470
+ /* Select (native <select>, themed to match RangeControl/Buttons) */
471
+ .stats-select {
472
+ appearance: none;
473
+ padding: 6px 28px 6px 12px;
474
+ font-size: 13px;
475
+ font-weight: 500;
476
+ font-family: inherit;
477
+ color: var(--text);
478
+ background-color: var(--surface-2);
479
+ /* Chevron follows theme: light stroke on dark, dark stroke on light. */
480
+ background-image: var(--select-chevron);
481
+ background-repeat: no-repeat;
482
+ background-position: right 10px center;
483
+ border: 1px solid var(--border);
484
+ border-radius: var(--radius-md);
485
+ cursor: pointer;
486
+ transition: border-color 0.15s ease, background-color 0.15s ease;
487
+ max-width: 100%;
488
+ text-overflow: ellipsis;
489
+ }
490
+
491
+ .stats-select:hover:not(:disabled) {
492
+ border-color: var(--border-strong);
493
+ background-color: var(--bg-hover);
494
+ }
495
+
496
+ .stats-select:focus-visible {
497
+ outline: 2px solid var(--link);
498
+ outline-offset: 1px;
499
+ }
500
+
501
+ .stats-select:disabled {
502
+ opacity: 0.5;
503
+ cursor: not-allowed;
504
+ }
505
+
506
+ /* Prevent iOS from zooming the viewport when a select receives focus. */
507
+ @media (pointer: coarse) {
508
+ .stats-select {
509
+ font-size: 16px;
510
+ }
511
+ }
512
+
513
+ /* Dropdown list itself follows the page theme via color-scheme. */
514
+ .stats-select option {
515
+ background-color: var(--surface);
516
+ color: var(--text);
517
+ }
518
+ /* Forced-colors (Windows High Contrast) suppresses background images, so the
519
+ custom chevron vanishes; restore the native indicator instead. */
520
+ @media (forced-colors: active) {
521
+ .stats-select {
522
+ appearance: auto;
523
+ }
524
+ }
525
+
520
526
  /* Sync Button & Container */
521
527
  .stats-sync-container {
522
528
  display: flex;
@@ -1287,7 +1293,7 @@
1287
1293
  }
1288
1294
 
1289
1295
  /* ==========================================================================
1290
- Brand mark on the Zeta wordmark (both rail + mobile drawer headers)
1296
+ Brand mark on the OMP wordmark (both rail + mobile drawer headers)
1291
1297
  ========================================================================== */
1292
1298
  .stats-logo-container {
1293
1299
  display: grid;
@@ -1302,7 +1308,7 @@
1302
1308
  width: 16px;
1303
1309
  height: 16px;
1304
1310
  border-radius: 4px;
1305
- background: linear-gradient(135deg, #1d4ed8 0%, #2563eb 50%, #60a5fa 100%); /* Zeta brand gradient mark */
1311
+ background: linear-gradient(135deg, #ed4abf 0%, #9b4dff 50%, #5ad8e6 100%); /* OMP brand gradient mark */
1306
1312
  }
1307
1313
 
1308
1314
  .stats-logo-text,
@@ -1382,3 +1388,319 @@
1382
1388
  transition: none;
1383
1389
  }
1384
1390
  }
1391
+
1392
+ /* ==========================================================================
1393
+ Traces (session flamegraph)
1394
+ ========================================================================== */
1395
+ .stats-trace-view {
1396
+ display: flex;
1397
+ flex-direction: column;
1398
+ gap: 14px;
1399
+ }
1400
+
1401
+ .stats-trace-header {
1402
+ display: flex;
1403
+ align-items: center;
1404
+ gap: 12px;
1405
+ flex-wrap: wrap;
1406
+ min-width: 0;
1407
+ }
1408
+
1409
+ .stats-trace-back {
1410
+ display: inline-flex;
1411
+ align-items: center;
1412
+ gap: 6px;
1413
+ padding: 6px 12px;
1414
+ font-size: 12px;
1415
+ color: var(--muted);
1416
+ background: var(--surface);
1417
+ border: 1px solid var(--border);
1418
+ border-radius: var(--radius-md);
1419
+ cursor: pointer;
1420
+ transition: color 0.14s ease, border-color 0.14s ease;
1421
+ }
1422
+
1423
+ .stats-trace-back:hover {
1424
+ color: var(--text);
1425
+ border-color: var(--border-strong);
1426
+ }
1427
+
1428
+ .stats-trace-back:focus-visible {
1429
+ outline: 2px solid var(--link);
1430
+ outline-offset: 2px;
1431
+ }
1432
+
1433
+ .stats-trace-title {
1434
+ font-size: 15px;
1435
+ font-weight: 600;
1436
+ color: var(--text);
1437
+ margin: 0;
1438
+ min-width: 0;
1439
+ overflow: hidden;
1440
+ text-overflow: ellipsis;
1441
+ white-space: nowrap;
1442
+ }
1443
+
1444
+ .stats-trace-cwd {
1445
+ font-size: 11px;
1446
+ color: var(--dim);
1447
+ border: 1px solid var(--border);
1448
+ border-radius: 999px;
1449
+ padding: 3px 10px;
1450
+ max-width: 280px;
1451
+ overflow: hidden;
1452
+ text-overflow: ellipsis;
1453
+ white-space: nowrap;
1454
+ }
1455
+
1456
+ /* Summary strip: one bordered card, hairline-divided stat columns. */
1457
+ .stats-trace-summary {
1458
+ display: flex;
1459
+ flex-wrap: wrap;
1460
+ background: var(--surface);
1461
+ border: 1px solid var(--border);
1462
+ border-radius: var(--radius-lg);
1463
+ overflow: hidden;
1464
+ }
1465
+
1466
+ .stats-trace-summary-cell {
1467
+ display: flex;
1468
+ flex-direction: column;
1469
+ gap: 3px;
1470
+ padding: 10px 16px;
1471
+ border-right: 1px solid var(--border);
1472
+ min-width: 76px;
1473
+ }
1474
+
1475
+ .stats-trace-summary-cell:last-child {
1476
+ border-right: none;
1477
+ }
1478
+
1479
+ .stats-trace-summary-label {
1480
+ font-size: 9px;
1481
+ text-transform: uppercase;
1482
+ letter-spacing: 0.07em;
1483
+ color: var(--dim);
1484
+ }
1485
+
1486
+ .stats-trace-summary-value {
1487
+ font-size: 13px;
1488
+ font-weight: 500;
1489
+ color: var(--text);
1490
+ font-variant-numeric: tabular-nums;
1491
+ }
1492
+
1493
+ /* Toolbar row above the timeline. */
1494
+ .stats-trace-toolbar {
1495
+ display: flex;
1496
+ align-items: center;
1497
+ gap: 12px;
1498
+ flex-wrap: wrap;
1499
+ }
1500
+
1501
+ .stats-trace-toolbar-hint {
1502
+ font-size: 10px;
1503
+ color: var(--dim);
1504
+ margin-left: auto;
1505
+ }
1506
+
1507
+ .stats-trace-input {
1508
+ background: var(--surface);
1509
+ border: 1px solid var(--border);
1510
+ border-radius: var(--radius-md);
1511
+ color: var(--text);
1512
+ font-size: 12px;
1513
+ padding: 6px 10px;
1514
+ }
1515
+
1516
+ .stats-trace-input:focus-visible {
1517
+ outline: 2px solid var(--link);
1518
+ outline-offset: 1px;
1519
+ }
1520
+
1521
+ .stats-trace-input::placeholder {
1522
+ color: var(--dim);
1523
+ }
1524
+
1525
+ .stats-trace-check {
1526
+ display: inline-flex;
1527
+ align-items: center;
1528
+ gap: 6px;
1529
+ font-size: 12px;
1530
+ color: var(--muted);
1531
+ cursor: pointer;
1532
+ user-select: none;
1533
+ }
1534
+
1535
+ .stats-trace-icon-btn {
1536
+ display: inline-flex;
1537
+ align-items: center;
1538
+ justify-content: center;
1539
+ width: 26px;
1540
+ height: 26px;
1541
+ background: transparent;
1542
+ border: 1px solid var(--border);
1543
+ border-radius: var(--radius-md);
1544
+ color: var(--muted);
1545
+ cursor: pointer;
1546
+ transition: color 0.14s ease, border-color 0.14s ease;
1547
+ }
1548
+
1549
+ .stats-trace-icon-btn:hover:not(:disabled) {
1550
+ color: var(--text);
1551
+ border-color: var(--border-strong);
1552
+ }
1553
+
1554
+ .stats-trace-icon-btn:focus-visible {
1555
+ outline: 2px solid var(--link);
1556
+ outline-offset: 2px;
1557
+ }
1558
+
1559
+ .stats-trace-icon-btn:disabled {
1560
+ opacity: 0.4;
1561
+ cursor: default;
1562
+ }
1563
+
1564
+ /* Timeline card: minimap + canvas share one bordered surface. */
1565
+ .stats-trace-timeline-card {
1566
+ background: var(--surface);
1567
+ border: 1px solid var(--border);
1568
+ border-radius: var(--radius-lg);
1569
+ padding: 12px;
1570
+ display: flex;
1571
+ flex-direction: column;
1572
+ gap: 10px;
1573
+ }
1574
+
1575
+ .stats-trace-timeline {
1576
+ position: relative;
1577
+ display: flex;
1578
+ max-height: 560px;
1579
+ overflow-y: auto;
1580
+ overflow-x: hidden;
1581
+ }
1582
+
1583
+ .stats-trace-canvas {
1584
+ cursor: grab;
1585
+ outline: none;
1586
+ touch-action: none;
1587
+ border-radius: var(--radius-sm);
1588
+ }
1589
+
1590
+ .stats-trace-canvas:focus-visible {
1591
+ outline: 2px solid var(--link);
1592
+ outline-offset: 2px;
1593
+ }
1594
+
1595
+ .stats-trace-canvas:active {
1596
+ cursor: grabbing;
1597
+ }
1598
+
1599
+ .stats-trace-chevron {
1600
+ display: inline-flex;
1601
+ background: none;
1602
+ border: none;
1603
+ padding: 0;
1604
+ cursor: pointer;
1605
+ color: var(--muted);
1606
+ }
1607
+
1608
+ .stats-trace-chevron:hover {
1609
+ color: var(--text);
1610
+ }
1611
+
1612
+ .stats-trace-gutter-track {
1613
+ font-size: 11px;
1614
+ font-weight: 500;
1615
+ color: var(--text);
1616
+ }
1617
+
1618
+ .stats-trace-gutter-model {
1619
+ font-size: 9px;
1620
+ color: var(--dim);
1621
+ }
1622
+
1623
+ .stats-trace-gutter-lane {
1624
+ position: absolute;
1625
+ font-size: 8px;
1626
+ color: var(--dim);
1627
+ text-transform: uppercase;
1628
+ letter-spacing: 0.08em;
1629
+ }
1630
+
1631
+ /* Transcript list under the timeline. */
1632
+ .stats-trace-list {
1633
+ background: var(--surface);
1634
+ border: 1px solid var(--border);
1635
+ border-radius: var(--radius-lg);
1636
+ height: 340px;
1637
+ overflow-y: auto;
1638
+ }
1639
+
1640
+ .stats-trace-row {
1641
+ display: flex;
1642
+ align-items: center;
1643
+ gap: 10px;
1644
+ height: 28px;
1645
+ width: 100%;
1646
+ padding: 0 12px;
1647
+ background: transparent;
1648
+ border: none;
1649
+ border-bottom: 1px solid var(--border);
1650
+ text-align: left;
1651
+ cursor: pointer;
1652
+ transition: background 0.1s ease;
1653
+ }
1654
+
1655
+ .stats-trace-row:hover {
1656
+ background: var(--bg-hover);
1657
+ }
1658
+
1659
+ .stats-trace-row:focus-visible {
1660
+ outline: 2px solid var(--link);
1661
+ outline-offset: -2px;
1662
+ }
1663
+
1664
+ .stats-trace-row[data-selected="true"] {
1665
+ background: var(--accent-muted);
1666
+ }
1667
+
1668
+ .stats-trace-row-static {
1669
+ cursor: default;
1670
+ }
1671
+
1672
+ .stats-trace-row-static:hover {
1673
+ background: transparent;
1674
+ }
1675
+
1676
+ .stats-trace-kind {
1677
+ flex-shrink: 0;
1678
+ width: 58px;
1679
+ font-size: 8px;
1680
+ text-transform: uppercase;
1681
+ letter-spacing: 0.06em;
1682
+ border-radius: 3px;
1683
+ padding: 2px 0;
1684
+ text-align: center;
1685
+ overflow: hidden;
1686
+ text-overflow: ellipsis;
1687
+ white-space: nowrap;
1688
+ }
1689
+
1690
+ .stats-trace-row-meta {
1691
+ flex-shrink: 0;
1692
+ margin-left: auto;
1693
+ font-size: 10px;
1694
+ color: var(--dim);
1695
+ font-variant-numeric: tabular-nums;
1696
+ display: flex;
1697
+ gap: 10px;
1698
+ }
1699
+
1700
+ @media (prefers-reduced-motion: reduce) {
1701
+ .stats-trace-back,
1702
+ .stats-trace-icon-btn,
1703
+ .stats-trace-row {
1704
+ transition: none;
1705
+ }
1706
+ }
@@ -0,0 +1,88 @@
1
+ /**
2
+ * Collapsible per-tool duration aggregates for one trace, sorted by total
3
+ * time descending (as produced by the server).
4
+ */
5
+
6
+ import { ChevronDown, ChevronRight } from "lucide-react";
7
+ import { useMemo, useState } from "react";
8
+ import { formatDurationMs, formatInteger } from "../data/formatters";
9
+ import type { TraceToolStat } from "../types";
10
+ import { DataTable } from "../ui/DataTable";
11
+
12
+ export interface AggregatesPanelProps {
13
+ toolStats: TraceToolStat[];
14
+ }
15
+
16
+ export function AggregatesPanel({ toolStats }: AggregatesPanelProps) {
17
+ const [open, setOpen] = useState(false);
18
+
19
+ const columns = useMemo(
20
+ () => [
21
+ { key: "tool", header: "Tool", render: (item: TraceToolStat) => item.tool },
22
+ { key: "calls", header: "Calls", numeric: true, render: (item: TraceToolStat) => formatInteger(item.calls) },
23
+ {
24
+ key: "errors",
25
+ header: "Errors",
26
+ numeric: true,
27
+ render: (item: TraceToolStat) => formatInteger(item.errors),
28
+ },
29
+ {
30
+ key: "total",
31
+ header: "Total",
32
+ numeric: true,
33
+ render: (item: TraceToolStat) => formatDurationMs(item.totalMs),
34
+ },
35
+ {
36
+ key: "avg",
37
+ header: "Avg",
38
+ numeric: true,
39
+ render: (item: TraceToolStat) => formatDurationMs(item.calls > 0 ? item.totalMs / item.calls : 0),
40
+ },
41
+ { key: "max", header: "Max", numeric: true, render: (item: TraceToolStat) => formatDurationMs(item.maxMs) },
42
+ ],
43
+ [],
44
+ );
45
+
46
+ if (toolStats.length === 0) return null;
47
+
48
+ return (
49
+ <div className="stats-panel">
50
+ <button
51
+ type="button"
52
+ onClick={() => setOpen(prev => !prev)}
53
+ aria-expanded={open}
54
+ style={{
55
+ display: "flex",
56
+ alignItems: "center",
57
+ gap: 6,
58
+ width: "100%",
59
+ background: "none",
60
+ border: "none",
61
+ padding: "10px 14px",
62
+ cursor: "pointer",
63
+ textAlign: "left",
64
+ }}
65
+ >
66
+ {open ? (
67
+ <ChevronDown size={14} className="stats-text-muted" aria-hidden="true" />
68
+ ) : (
69
+ <ChevronRight size={14} className="stats-text-muted" aria-hidden="true" />
70
+ )}
71
+ <span className="stats-panel-title">Tool Aggregates</span>
72
+ <span className="stats-text-muted" style={{ fontSize: 11 }}>
73
+ {toolStats.length} tools
74
+ </span>
75
+ </button>
76
+ {open && (
77
+ <div className="stats-panel-body">
78
+ <DataTable
79
+ columns={columns}
80
+ data={toolStats}
81
+ keyExtractor={item => item.tool}
82
+ emptyText="No tool calls"
83
+ />
84
+ </div>
85
+ )}
86
+ </div>
87
+ );
88
+ }