twining-mcp 1.4.2 → 1.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.
- package/README.md +51 -2
- package/dist/analytics/analytics-engine.d.ts +18 -0
- package/dist/analytics/analytics-engine.d.ts.map +1 -0
- package/dist/analytics/analytics-engine.js +114 -0
- package/dist/analytics/analytics-engine.js.map +1 -0
- package/dist/analytics/instrumented-server.d.ts +12 -0
- package/dist/analytics/instrumented-server.d.ts.map +1 -0
- package/dist/analytics/instrumented-server.js +76 -0
- package/dist/analytics/instrumented-server.js.map +1 -0
- package/dist/analytics/metrics-collector.d.ts +15 -0
- package/dist/analytics/metrics-collector.d.ts.map +1 -0
- package/dist/analytics/metrics-collector.js +36 -0
- package/dist/analytics/metrics-collector.js.map +1 -0
- package/dist/analytics/metrics-store.d.ts +20 -0
- package/dist/analytics/metrics-store.d.ts.map +1 -0
- package/dist/analytics/metrics-store.js +109 -0
- package/dist/analytics/metrics-store.js.map +1 -0
- package/dist/analytics/telemetry-client.d.ts +19 -0
- package/dist/analytics/telemetry-client.d.ts.map +1 -0
- package/dist/analytics/telemetry-client.js +137 -0
- package/dist/analytics/telemetry-client.js.map +1 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +10 -0
- package/dist/config.js.map +1 -1
- package/dist/dashboard/api-routes.d.ts.map +1 -1
- package/dist/dashboard/api-routes.js +71 -0
- package/dist/dashboard/api-routes.js.map +1 -1
- package/dist/dashboard/public/app.js +185 -0
- package/dist/dashboard/public/index.html +86 -1
- package/dist/dashboard/public/style.css +83 -0
- package/dist/index.js +23 -1
- package/dist/index.js.map +1 -1
- package/dist/server.d.ts +8 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +8 -5
- package/dist/server.js.map +1 -1
- package/dist/storage/init.js +1 -1
- package/dist/storage/init.js.map +1 -1
- package/dist/utils/types.d.ts +79 -0
- package/dist/utils/types.d.ts.map +1 -1
- package/package.json +4 -1
- package/src/dashboard/public/app.js +185 -0
- package/src/dashboard/public/index.html +86 -1
- package/src/dashboard/public/style.css +83 -0
|
@@ -18,6 +18,7 @@ var state = {
|
|
|
18
18
|
delegations: { data: [], sortKey: "timestamp", sortDir: "desc", page: 1, pageSize: 25, selectedId: null },
|
|
19
19
|
handoffs: { data: [], sortKey: "created_at", sortDir: "desc", page: 1, pageSize: 25, selectedId: null },
|
|
20
20
|
agentsSubView: "agents-list",
|
|
21
|
+
insights: { valueStats: null, toolUsage: [], errors: [] },
|
|
21
22
|
globalScope: "",
|
|
22
23
|
status: null,
|
|
23
24
|
pollTimer: null,
|
|
@@ -278,6 +279,44 @@ function fetchHandoffDetail(id) {
|
|
|
278
279
|
});
|
|
279
280
|
}
|
|
280
281
|
|
|
282
|
+
/* ========== Insights Fetching ========== */
|
|
283
|
+
|
|
284
|
+
function fetchValueStats() {
|
|
285
|
+
fetch("/api/analytics/value-stats")
|
|
286
|
+
.then(function(res) { return res.json(); })
|
|
287
|
+
.then(function(data) {
|
|
288
|
+
state.insights.valueStats = data;
|
|
289
|
+
renderValueStats();
|
|
290
|
+
})
|
|
291
|
+
.catch(function() {});
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
function fetchToolUsage() {
|
|
295
|
+
fetch("/api/analytics/tool-usage")
|
|
296
|
+
.then(function(res) { return res.json(); })
|
|
297
|
+
.then(function(data) {
|
|
298
|
+
state.insights.toolUsage = data.tools || [];
|
|
299
|
+
renderToolUsage();
|
|
300
|
+
})
|
|
301
|
+
.catch(function() {});
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
function fetchErrorBreakdown() {
|
|
305
|
+
fetch("/api/analytics/errors")
|
|
306
|
+
.then(function(res) { return res.json(); })
|
|
307
|
+
.then(function(data) {
|
|
308
|
+
state.insights.errors = data.errors || [];
|
|
309
|
+
renderErrorBreakdown();
|
|
310
|
+
})
|
|
311
|
+
.catch(function() {});
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
function fetchInsights() {
|
|
315
|
+
fetchValueStats();
|
|
316
|
+
fetchToolUsage();
|
|
317
|
+
fetchErrorBreakdown();
|
|
318
|
+
}
|
|
319
|
+
|
|
281
320
|
/* ========== Polling Lifecycle ========== */
|
|
282
321
|
|
|
283
322
|
function refreshData() {
|
|
@@ -293,6 +332,7 @@ function refreshData() {
|
|
|
293
332
|
else if (state.agentsSubView === "delegations") fetchDelegations();
|
|
294
333
|
else if (state.agentsSubView === "handoffs") fetchHandoffs();
|
|
295
334
|
}
|
|
335
|
+
else if (tab === "insights") fetchInsights();
|
|
296
336
|
}
|
|
297
337
|
|
|
298
338
|
function startPolling() {
|
|
@@ -456,6 +496,13 @@ function renderStatus() {
|
|
|
456
496
|
setVal("stat-pending-delegations", s.pending_delegations);
|
|
457
497
|
setVal("stat-total-handoffs", s.total_handoffs);
|
|
458
498
|
|
|
499
|
+
// Update project name in header and page title
|
|
500
|
+
if (s.project_name) {
|
|
501
|
+
var titleEl = document.getElementById("dashboard-title");
|
|
502
|
+
if (titleEl) titleEl.textContent = s.project_name + " — Twining";
|
|
503
|
+
document.title = s.project_name + " — Twining Dashboard";
|
|
504
|
+
}
|
|
505
|
+
|
|
459
506
|
var msgEl = document.getElementById("uninitialized-msg");
|
|
460
507
|
if (msgEl) {
|
|
461
508
|
msgEl.style.display = (s.initialized === false) ? "block" : "none";
|
|
@@ -2391,6 +2438,144 @@ function updateGraphData() {
|
|
|
2391
2438
|
}
|
|
2392
2439
|
}
|
|
2393
2440
|
|
|
2441
|
+
/* ========== Insights Rendering ========== */
|
|
2442
|
+
|
|
2443
|
+
function renderValueStats() {
|
|
2444
|
+
var vs = state.insights.valueStats;
|
|
2445
|
+
if (!vs) return;
|
|
2446
|
+
|
|
2447
|
+
// Blind decisions prevented
|
|
2448
|
+
var prevRate = document.getElementById("insight-prevention-rate");
|
|
2449
|
+
var prevDetail = document.getElementById("insight-prevention-detail");
|
|
2450
|
+
if (prevRate) prevRate.textContent = Math.round(vs.blind_decisions_prevented.prevention_rate * 100) + "%";
|
|
2451
|
+
if (prevDetail) prevDetail.textContent = vs.blind_decisions_prevented.assembled_before + " of " + vs.blind_decisions_prevented.total_decisions + " decisions had context assembled first";
|
|
2452
|
+
|
|
2453
|
+
// Warnings surfaced
|
|
2454
|
+
var warnTotal = document.getElementById("insight-warnings-total");
|
|
2455
|
+
var warnDetail = document.getElementById("insight-warnings-detail");
|
|
2456
|
+
if (warnTotal) warnTotal.textContent = vs.warnings_surfaced.total;
|
|
2457
|
+
if (warnDetail) warnDetail.textContent = vs.warnings_surfaced.acknowledged + " acknowledged, " + vs.warnings_surfaced.resolved + " resolved, " + vs.warnings_surfaced.ignored + " unaddressed";
|
|
2458
|
+
|
|
2459
|
+
// Test coverage
|
|
2460
|
+
var testCov = document.getElementById("insight-test-coverage");
|
|
2461
|
+
var testDetail = document.getElementById("insight-test-detail");
|
|
2462
|
+
if (testCov) testCov.textContent = Math.round(vs.test_coverage.coverage_rate * 100) + "%";
|
|
2463
|
+
if (testDetail) testDetail.textContent = vs.test_coverage.with_tested_by + " of " + vs.test_coverage.total_decisions + " decisions have tested_by relations";
|
|
2464
|
+
|
|
2465
|
+
// Commit traceability
|
|
2466
|
+
var traceVal = document.getElementById("insight-traceability");
|
|
2467
|
+
var traceDetail = document.getElementById("insight-traceability-detail");
|
|
2468
|
+
if (traceVal) traceVal.textContent = Math.round(vs.commit_traceability.traceability_rate * 100) + "%";
|
|
2469
|
+
if (traceDetail) traceDetail.textContent = vs.commit_traceability.with_commits + " of " + vs.commit_traceability.total_decisions + " decisions linked to commits";
|
|
2470
|
+
|
|
2471
|
+
// Decision lifecycle
|
|
2472
|
+
var lcVal = document.getElementById("insight-lifecycle");
|
|
2473
|
+
var lcDetail = document.getElementById("insight-lifecycle-detail");
|
|
2474
|
+
var lc = vs.decision_lifecycle;
|
|
2475
|
+
if (lcVal) lcVal.textContent = (lc.active + lc.provisional + lc.superseded + lc.overridden);
|
|
2476
|
+
if (lcDetail) lcDetail.textContent = lc.active + " active, " + lc.provisional + " provisional, " + lc.superseded + " superseded, " + lc.overridden + " overridden";
|
|
2477
|
+
|
|
2478
|
+
// Knowledge graph
|
|
2479
|
+
var graphVal = document.getElementById("insight-graph");
|
|
2480
|
+
var graphDetail = document.getElementById("insight-graph-detail");
|
|
2481
|
+
if (graphVal) graphVal.textContent = vs.knowledge_graph.entities + " / " + vs.knowledge_graph.relations;
|
|
2482
|
+
if (graphDetail) {
|
|
2483
|
+
var typeStrs = [];
|
|
2484
|
+
var ebt = vs.knowledge_graph.entities_by_type;
|
|
2485
|
+
for (var ek in ebt) { if (ebt.hasOwnProperty(ek)) typeStrs.push(ek + ": " + ebt[ek]); }
|
|
2486
|
+
graphDetail.textContent = typeStrs.length > 0 ? "Entities: " + typeStrs.join(", ") : "No entities";
|
|
2487
|
+
}
|
|
2488
|
+
|
|
2489
|
+
// Agent coordination
|
|
2490
|
+
var coordVal = document.getElementById("insight-coordination");
|
|
2491
|
+
var coordDetail = document.getElementById("insight-coordination-detail");
|
|
2492
|
+
if (coordVal) coordVal.textContent = vs.agent_coordination.total_handoffs;
|
|
2493
|
+
if (coordDetail) coordDetail.textContent = "Acknowledgment rate: " + Math.round(vs.agent_coordination.acknowledgment_rate * 100) + "%";
|
|
2494
|
+
}
|
|
2495
|
+
|
|
2496
|
+
function renderToolUsage() {
|
|
2497
|
+
var tbody = document.querySelector("#tool-usage-table tbody");
|
|
2498
|
+
if (!tbody) return;
|
|
2499
|
+
clearElement(tbody);
|
|
2500
|
+
|
|
2501
|
+
var tools = state.insights.toolUsage;
|
|
2502
|
+
if (tools.length === 0) {
|
|
2503
|
+
var row = document.createElement("tr");
|
|
2504
|
+
var cell = document.createElement("td");
|
|
2505
|
+
cell.colSpan = 6;
|
|
2506
|
+
cell.textContent = "No tool calls recorded";
|
|
2507
|
+
cell.className = "placeholder";
|
|
2508
|
+
row.appendChild(cell);
|
|
2509
|
+
tbody.appendChild(row);
|
|
2510
|
+
return;
|
|
2511
|
+
}
|
|
2512
|
+
|
|
2513
|
+
for (var i = 0; i < tools.length; i++) {
|
|
2514
|
+
var t = tools[i];
|
|
2515
|
+
var tr = document.createElement("tr");
|
|
2516
|
+
|
|
2517
|
+
var tdName = document.createElement("td");
|
|
2518
|
+
tdName.textContent = t.tool_name;
|
|
2519
|
+
tr.appendChild(tdName);
|
|
2520
|
+
|
|
2521
|
+
var tdCalls = document.createElement("td");
|
|
2522
|
+
tdCalls.textContent = t.call_count;
|
|
2523
|
+
tr.appendChild(tdCalls);
|
|
2524
|
+
|
|
2525
|
+
var tdErrors = document.createElement("td");
|
|
2526
|
+
tdErrors.textContent = t.error_count;
|
|
2527
|
+
if (t.error_count > 0) tdErrors.className = "error-count";
|
|
2528
|
+
tr.appendChild(tdErrors);
|
|
2529
|
+
|
|
2530
|
+
var tdAvg = document.createElement("td");
|
|
2531
|
+
tdAvg.textContent = t.avg_duration_ms;
|
|
2532
|
+
tr.appendChild(tdAvg);
|
|
2533
|
+
|
|
2534
|
+
var tdP95 = document.createElement("td");
|
|
2535
|
+
tdP95.textContent = t.p95_duration_ms;
|
|
2536
|
+
tr.appendChild(tdP95);
|
|
2537
|
+
|
|
2538
|
+
var tdLast = document.createElement("td");
|
|
2539
|
+
tdLast.textContent = formatTime(t.last_called);
|
|
2540
|
+
tr.appendChild(tdLast);
|
|
2541
|
+
|
|
2542
|
+
tbody.appendChild(tr);
|
|
2543
|
+
}
|
|
2544
|
+
}
|
|
2545
|
+
|
|
2546
|
+
function renderErrorBreakdown() {
|
|
2547
|
+
var tbody = document.querySelector("#error-breakdown-table tbody");
|
|
2548
|
+
var noMsg = document.getElementById("no-errors-msg");
|
|
2549
|
+
if (!tbody) return;
|
|
2550
|
+
clearElement(tbody);
|
|
2551
|
+
|
|
2552
|
+
var errors = state.insights.errors;
|
|
2553
|
+
if (errors.length === 0) {
|
|
2554
|
+
if (noMsg) noMsg.style.display = "block";
|
|
2555
|
+
return;
|
|
2556
|
+
}
|
|
2557
|
+
if (noMsg) noMsg.style.display = "none";
|
|
2558
|
+
|
|
2559
|
+
for (var i = 0; i < errors.length; i++) {
|
|
2560
|
+
var e = errors[i];
|
|
2561
|
+
var tr = document.createElement("tr");
|
|
2562
|
+
|
|
2563
|
+
var tdTool = document.createElement("td");
|
|
2564
|
+
tdTool.textContent = e.tool_name;
|
|
2565
|
+
tr.appendChild(tdTool);
|
|
2566
|
+
|
|
2567
|
+
var tdCode = document.createElement("td");
|
|
2568
|
+
tdCode.textContent = e.error_code;
|
|
2569
|
+
tr.appendChild(tdCode);
|
|
2570
|
+
|
|
2571
|
+
var tdCount = document.createElement("td");
|
|
2572
|
+
tdCount.textContent = e.count;
|
|
2573
|
+
tr.appendChild(tdCount);
|
|
2574
|
+
|
|
2575
|
+
tbody.appendChild(tr);
|
|
2576
|
+
}
|
|
2577
|
+
}
|
|
2578
|
+
|
|
2394
2579
|
/* ========== Initialization ========== */
|
|
2395
2580
|
|
|
2396
2581
|
document.addEventListener("DOMContentLoaded", function() {
|
|
@@ -17,7 +17,10 @@
|
|
|
17
17
|
<header>
|
|
18
18
|
<div class="header-brand">
|
|
19
19
|
<img src="assets/favicon.svg" alt="Twining" width="28" height="28" class="header-logo">
|
|
20
|
-
<h1>Twining Dashboard</h1>
|
|
20
|
+
<h1 id="dashboard-title">Twining Dashboard</h1>
|
|
21
|
+
<a href="https://github.com/twining-mcp/twining-mcp" target="_blank" rel="noopener noreferrer" class="github-link" title="View on GitHub">
|
|
22
|
+
<svg width="20" height="20" viewBox="0 0 16 16" fill="currentColor"><path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z"/></svg>
|
|
23
|
+
</a>
|
|
21
24
|
</div>
|
|
22
25
|
<div class="global-scope-filter">
|
|
23
26
|
<label for="global-scope">Scope:</label>
|
|
@@ -46,6 +49,7 @@
|
|
|
46
49
|
<button class="tab-btn" data-tab="graph">Graph</button>
|
|
47
50
|
<button class="tab-btn" data-tab="search">Search</button>
|
|
48
51
|
<button class="tab-btn" data-tab="agents">Agents</button>
|
|
52
|
+
<button class="tab-btn" data-tab="insights">Insights</button>
|
|
49
53
|
</nav>
|
|
50
54
|
|
|
51
55
|
<div class="search-bar">
|
|
@@ -347,6 +351,87 @@
|
|
|
347
351
|
</div>
|
|
348
352
|
</div>
|
|
349
353
|
</div>
|
|
354
|
+
|
|
355
|
+
<!-- Insights Tab -->
|
|
356
|
+
<div class="tab-content" id="tab-insights" style="display:none">
|
|
357
|
+
<h3 class="insights-heading">Value Metrics</h3>
|
|
358
|
+
<div class="insights-grid">
|
|
359
|
+
<div class="insight-card">
|
|
360
|
+
<div class="insight-icon">🛡</div>
|
|
361
|
+
<div class="insight-label">Blind Decisions Prevented</div>
|
|
362
|
+
<div class="insight-value" id="insight-prevention-rate">--</div>
|
|
363
|
+
<div class="insight-detail" id="insight-prevention-detail"></div>
|
|
364
|
+
</div>
|
|
365
|
+
<div class="insight-card">
|
|
366
|
+
<div class="insight-icon">⚠</div>
|
|
367
|
+
<div class="insight-label">Warnings Surfaced</div>
|
|
368
|
+
<div class="insight-value" id="insight-warnings-total">--</div>
|
|
369
|
+
<div class="insight-detail" id="insight-warnings-detail"></div>
|
|
370
|
+
</div>
|
|
371
|
+
<div class="insight-card">
|
|
372
|
+
<div class="insight-icon">✅</div>
|
|
373
|
+
<div class="insight-label">Test Coverage</div>
|
|
374
|
+
<div class="insight-value" id="insight-test-coverage">--</div>
|
|
375
|
+
<div class="insight-detail" id="insight-test-detail"></div>
|
|
376
|
+
</div>
|
|
377
|
+
<div class="insight-card">
|
|
378
|
+
<div class="insight-icon">🔗</div>
|
|
379
|
+
<div class="insight-label">Commit Traceability</div>
|
|
380
|
+
<div class="insight-value" id="insight-traceability">--</div>
|
|
381
|
+
<div class="insight-detail" id="insight-traceability-detail"></div>
|
|
382
|
+
</div>
|
|
383
|
+
<div class="insight-card">
|
|
384
|
+
<div class="insight-icon">📊</div>
|
|
385
|
+
<div class="insight-label">Decision Lifecycle</div>
|
|
386
|
+
<div class="insight-value" id="insight-lifecycle">--</div>
|
|
387
|
+
<div class="insight-detail" id="insight-lifecycle-detail"></div>
|
|
388
|
+
</div>
|
|
389
|
+
<div class="insight-card">
|
|
390
|
+
<div class="insight-icon">🕸</div>
|
|
391
|
+
<div class="insight-label">Knowledge Graph</div>
|
|
392
|
+
<div class="insight-value" id="insight-graph">--</div>
|
|
393
|
+
<div class="insight-detail" id="insight-graph-detail"></div>
|
|
394
|
+
</div>
|
|
395
|
+
<div class="insight-card">
|
|
396
|
+
<div class="insight-icon">🤝</div>
|
|
397
|
+
<div class="insight-label">Agent Coordination</div>
|
|
398
|
+
<div class="insight-value" id="insight-coordination">--</div>
|
|
399
|
+
<div class="insight-detail" id="insight-coordination-detail"></div>
|
|
400
|
+
</div>
|
|
401
|
+
</div>
|
|
402
|
+
|
|
403
|
+
<h3 class="insights-heading">Tool Usage</h3>
|
|
404
|
+
<div id="insights-tool-usage">
|
|
405
|
+
<table class="data-table" id="tool-usage-table">
|
|
406
|
+
<thead>
|
|
407
|
+
<tr>
|
|
408
|
+
<th>Tool</th>
|
|
409
|
+
<th>Calls</th>
|
|
410
|
+
<th>Errors</th>
|
|
411
|
+
<th>Avg (ms)</th>
|
|
412
|
+
<th>P95 (ms)</th>
|
|
413
|
+
<th>Last Called</th>
|
|
414
|
+
</tr>
|
|
415
|
+
</thead>
|
|
416
|
+
<tbody></tbody>
|
|
417
|
+
</table>
|
|
418
|
+
</div>
|
|
419
|
+
|
|
420
|
+
<h3 class="insights-heading">Error Breakdown</h3>
|
|
421
|
+
<div id="insights-errors">
|
|
422
|
+
<table class="data-table" id="error-breakdown-table">
|
|
423
|
+
<thead>
|
|
424
|
+
<tr>
|
|
425
|
+
<th>Tool</th>
|
|
426
|
+
<th>Error Code</th>
|
|
427
|
+
<th>Count</th>
|
|
428
|
+
</tr>
|
|
429
|
+
</thead>
|
|
430
|
+
<tbody></tbody>
|
|
431
|
+
</table>
|
|
432
|
+
<p class="placeholder" id="no-errors-msg" style="display:none">No errors recorded</p>
|
|
433
|
+
</div>
|
|
434
|
+
</div>
|
|
350
435
|
</main>
|
|
351
436
|
|
|
352
437
|
<footer>
|
|
@@ -57,6 +57,16 @@ header h1 {
|
|
|
57
57
|
font-weight: 600;
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
+
.github-link {
|
|
61
|
+
color: var(--text-secondary, #6b7280);
|
|
62
|
+
display: flex;
|
|
63
|
+
align-items: center;
|
|
64
|
+
transition: color 0.15s;
|
|
65
|
+
}
|
|
66
|
+
.github-link:hover {
|
|
67
|
+
color: var(--text);
|
|
68
|
+
}
|
|
69
|
+
|
|
60
70
|
.header-status {
|
|
61
71
|
display: flex;
|
|
62
72
|
gap: 1.5rem;
|
|
@@ -976,3 +986,76 @@ tr.delegation-expired td { text-decoration: line-through; }
|
|
|
976
986
|
font-size: 0.75rem;
|
|
977
987
|
color: var(--muted);
|
|
978
988
|
}
|
|
989
|
+
|
|
990
|
+
/* ========== Insights Tab ========== */
|
|
991
|
+
|
|
992
|
+
.insights-heading {
|
|
993
|
+
margin: 1.5rem 0 0.75rem 0;
|
|
994
|
+
font-size: 1rem;
|
|
995
|
+
font-weight: 600;
|
|
996
|
+
color: var(--text);
|
|
997
|
+
}
|
|
998
|
+
|
|
999
|
+
.insights-heading:first-child {
|
|
1000
|
+
margin-top: 0;
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
.insights-grid {
|
|
1004
|
+
display: grid;
|
|
1005
|
+
grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
|
|
1006
|
+
gap: 1rem;
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
.insight-card {
|
|
1010
|
+
background: var(--card-bg);
|
|
1011
|
+
border: 1px solid var(--card-border);
|
|
1012
|
+
border-radius: 8px;
|
|
1013
|
+
padding: 1rem;
|
|
1014
|
+
text-align: center;
|
|
1015
|
+
transition: border-color 0.2s;
|
|
1016
|
+
}
|
|
1017
|
+
|
|
1018
|
+
.insight-card:hover {
|
|
1019
|
+
border-color: var(--accent);
|
|
1020
|
+
}
|
|
1021
|
+
|
|
1022
|
+
.insight-icon {
|
|
1023
|
+
font-size: 1.5rem;
|
|
1024
|
+
margin-bottom: 0.25rem;
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1027
|
+
.insight-label {
|
|
1028
|
+
font-size: 0.75rem;
|
|
1029
|
+
color: var(--muted);
|
|
1030
|
+
text-transform: uppercase;
|
|
1031
|
+
letter-spacing: 0.05em;
|
|
1032
|
+
margin-bottom: 0.25rem;
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1035
|
+
.insight-value {
|
|
1036
|
+
font-size: 1.75rem;
|
|
1037
|
+
font-weight: 700;
|
|
1038
|
+
color: var(--accent);
|
|
1039
|
+
margin-bottom: 0.25rem;
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
.insight-detail {
|
|
1043
|
+
font-size: 0.75rem;
|
|
1044
|
+
color: var(--muted);
|
|
1045
|
+
line-height: 1.4;
|
|
1046
|
+
}
|
|
1047
|
+
|
|
1048
|
+
#insights-tool-usage,
|
|
1049
|
+
#insights-errors {
|
|
1050
|
+
margin-bottom: 1.5rem;
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
#tool-usage-table td,
|
|
1054
|
+
#error-breakdown-table td {
|
|
1055
|
+
padding: 0.5rem 0.75rem;
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1058
|
+
.error-count {
|
|
1059
|
+
color: var(--error);
|
|
1060
|
+
font-weight: 600;
|
|
1061
|
+
}
|