agentacta 2026.3.12-r2 → 2026.3.26
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 +11 -1
- package/config.js +2 -0
- package/db.js +12 -0
- package/index.js +44 -2
- package/package.json +1 -1
- package/public/app.js +189 -3
- package/public/index.html +13 -10
- package/public/style.css +197 -16
package/README.md
CHANGED
|
@@ -39,7 +39,8 @@ AgentActa gives you one place to inspect the full trail.
|
|
|
39
39
|
- 🌗 Light and dark themes
|
|
40
40
|
- 📊 Stats for sessions, messages, tools, and tokens
|
|
41
41
|
- ⚡ Live indexing via file watching
|
|
42
|
-
- 📱 Mobile-
|
|
42
|
+
- 📱 Mobile-optimized UI with floating navigation
|
|
43
|
+
- 🏥 Session health scoring — reliability scores, issue detection, and per-signal breakdowns
|
|
43
44
|
- 💡 Search suggestions based on real data
|
|
44
45
|
- ⌨️ Command palette (⌘K / Ctrl+K) for quick navigation
|
|
45
46
|
- 🎨 Theme settings (system, light, dark, OLED)
|
|
@@ -90,6 +91,14 @@ Session types get tagged so noisy categories are easier to spot (cron, sub-agent
|
|
|
90
91
|
|
|
91
92
|
Pick a date, see everything that happened, newest first. Today's view updates live as new events come in.
|
|
92
93
|
|
|
94
|
+
### Insights
|
|
95
|
+
|
|
96
|
+
The Insights tab surfaces session health across your entire history.
|
|
97
|
+
|
|
98
|
+
It tracks five issue signals — repeated tool loops, sessions that produced no output, high error rates, vague instructions, and incomplete sessions. Each signal is severity-scaled so scores reflect how bad the problem actually was, not just whether it occurred.
|
|
99
|
+
|
|
100
|
+
The reliability score (0–100) is the inverse of the confusion score: higher means the agent completed work cleanly. The issue rate shows what percentage of possible signal types were detected in a session.
|
|
101
|
+
|
|
93
102
|
### File Activity
|
|
94
103
|
|
|
95
104
|
See what files were touched, how often, and by which sessions.
|
|
@@ -167,6 +176,7 @@ Default config (auto-generated on first run — session directories are detected
|
|
|
167
176
|
| `GET /api/timeline/stream?after=<ts>` | SSE stream for live timeline updates |
|
|
168
177
|
| `POST /api/maintenance` | VACUUM + WAL checkpoint (returns size before/after) |
|
|
169
178
|
| `GET /api/health` | Server status, version, uptime, session count |
|
|
179
|
+
| `GET /api/insights` | Session health summary — reliability scores, issue counts, top flagged sessions |
|
|
170
180
|
| `GET /api/export/search?q=<query>&format=md` | Export search results |
|
|
171
181
|
|
|
172
182
|
### Context API
|
package/config.js
CHANGED
|
@@ -57,6 +57,8 @@ function loadConfig() {
|
|
|
57
57
|
}
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
+
// In demo mode, ignore file-based sessionsPath so live data doesn't bleed in
|
|
61
|
+
if (process.env.AGENTACTA_DEMO_MODE) delete fileConfig.sessionsPath;
|
|
60
62
|
const config = { ...DEFAULTS, ...fileConfig };
|
|
61
63
|
|
|
62
64
|
// Env var overrides (highest priority)
|
package/db.js
CHANGED
|
@@ -103,6 +103,18 @@ function init(dbPath) {
|
|
|
103
103
|
);
|
|
104
104
|
|
|
105
105
|
CREATE INDEX IF NOT EXISTS idx_archive_session ON archive(session_id);
|
|
106
|
+
|
|
107
|
+
CREATE TABLE IF NOT EXISTS session_insights (
|
|
108
|
+
session_id TEXT PRIMARY KEY,
|
|
109
|
+
signals TEXT,
|
|
110
|
+
confusion_score INTEGER DEFAULT 0,
|
|
111
|
+
flagged INTEGER DEFAULT 0,
|
|
112
|
+
computed_at TEXT,
|
|
113
|
+
FOREIGN KEY (session_id) REFERENCES sessions(id) ON DELETE CASCADE
|
|
114
|
+
);
|
|
115
|
+
|
|
116
|
+
CREATE INDEX IF NOT EXISTS idx_insights_flagged ON session_insights(flagged);
|
|
117
|
+
CREATE INDEX IF NOT EXISTS idx_insights_score ON session_insights(confusion_score DESC);
|
|
106
118
|
`);
|
|
107
119
|
|
|
108
120
|
// Add columns if missing (migration)
|
package/index.js
CHANGED
|
@@ -20,6 +20,7 @@ if (process.argv.includes('--demo')) {
|
|
|
20
20
|
}
|
|
21
21
|
process.env.AGENTACTA_SESSIONS_PATH = demoDir;
|
|
22
22
|
process.env.AGENTACTA_DB_PATH = path.join(demoDir, 'demo.db');
|
|
23
|
+
process.env.AGENTACTA_DEMO_MODE = '1'; // signal to config.js to skip file-based sessionsPath
|
|
23
24
|
console.log(`Demo mode: using sessions from ${demoDir}`);
|
|
24
25
|
}
|
|
25
26
|
|
|
@@ -28,6 +29,7 @@ const { open, init, createStmts } = require('./db');
|
|
|
28
29
|
const { discoverSessionDirs, listJsonlFiles, indexFile } = require('./indexer');
|
|
29
30
|
const { attributeSessionEvents, attributeEventDelta } = require('./project-attribution');
|
|
30
31
|
const { loadDeltaAttributionContext } = require('./delta-attribution-context');
|
|
32
|
+
const { analyzeSession, analyzeAll, getInsightsSummary } = require('./insights');
|
|
31
33
|
|
|
32
34
|
const config = loadConfig();
|
|
33
35
|
const PORT = config.port;
|
|
@@ -167,6 +169,14 @@ for (const dir of sessionDirs) {
|
|
|
167
169
|
}
|
|
168
170
|
}
|
|
169
171
|
|
|
172
|
+
// Compute insights for all indexed sessions
|
|
173
|
+
try {
|
|
174
|
+
analyzeAll(db);
|
|
175
|
+
console.log('Insights computed for all sessions');
|
|
176
|
+
} catch (err) {
|
|
177
|
+
console.error('Error computing insights:', err.message);
|
|
178
|
+
}
|
|
179
|
+
|
|
170
180
|
console.log(`Watching ${sessionDirs.length} session directories`);
|
|
171
181
|
|
|
172
182
|
// Debounce map: filePath -> timeout handle
|
|
@@ -178,11 +188,18 @@ function reindexRecursiveDir(dir) {
|
|
|
178
188
|
try {
|
|
179
189
|
const files = listJsonlFiles(dir.path, true);
|
|
180
190
|
let changed = 0;
|
|
191
|
+
const upsert = db.prepare('INSERT OR REPLACE INTO session_insights (session_id, signals, confusion_score, flagged, computed_at) VALUES (?, ?, ?, ?, ?)');
|
|
181
192
|
for (const filePath of files) {
|
|
182
193
|
const result = indexFile(db, filePath, dir.agent, stmts, ARCHIVE_MODE, config);
|
|
183
194
|
if (!result.skipped) {
|
|
184
195
|
changed++;
|
|
185
|
-
if (result.sessionId)
|
|
196
|
+
if (result.sessionId) {
|
|
197
|
+
try {
|
|
198
|
+
const insight = analyzeSession(db, result.sessionId);
|
|
199
|
+
if (insight) upsert.run(insight.session_id, JSON.stringify(insight.signals), insight.confusion_score, insight.flagged ? 1 : 0, insight.computed_at);
|
|
200
|
+
} catch {}
|
|
201
|
+
sseEmitter.emit('session-update', result.sessionId);
|
|
202
|
+
}
|
|
186
203
|
}
|
|
187
204
|
}
|
|
188
205
|
if (changed > 0) console.log(`Live re-indexed ${changed} files (${dir.agent})`);
|
|
@@ -215,7 +232,10 @@ for (const dir of sessionDirs) {
|
|
|
215
232
|
const result = indexFile(db, filePath, dir.agent, stmts, ARCHIVE_MODE, config);
|
|
216
233
|
if (!result.skipped) {
|
|
217
234
|
console.log(`Live re-indexed: ${filename} (${dir.agent})`);
|
|
218
|
-
if (result.sessionId)
|
|
235
|
+
if (result.sessionId) {
|
|
236
|
+
try { analyzeSession(db, result.sessionId); const upsert = db.prepare('INSERT OR REPLACE INTO session_insights (session_id, signals, confusion_score, flagged, computed_at) VALUES (?, ?, ?, ?, ?)'); const insight = analyzeSession(db, result.sessionId); if (insight) upsert.run(insight.session_id, JSON.stringify(insight.signals), insight.confusion_score, insight.flagged ? 1 : 0, insight.computed_at); } catch {}
|
|
237
|
+
sseEmitter.emit('session-update', result.sessionId);
|
|
238
|
+
}
|
|
219
239
|
}
|
|
220
240
|
} catch (err) {
|
|
221
241
|
console.error(`Error re-indexing ${filename}:`, err.message);
|
|
@@ -239,6 +259,7 @@ const server = http.createServer((req, res) => {
|
|
|
239
259
|
if (pathname === '/api/reindex') {
|
|
240
260
|
const { indexAll } = require('./indexer');
|
|
241
261
|
const result = indexAll(db, config);
|
|
262
|
+
try { analyzeAll(db); } catch (e) { console.error('Insights recompute error:', e.message); }
|
|
242
263
|
return json(res, { ok: true, sessions: result.sessions, events: result.events });
|
|
243
264
|
}
|
|
244
265
|
|
|
@@ -764,6 +785,27 @@ const server = http.createServer((req, res) => {
|
|
|
764
785
|
`).all(fp);
|
|
765
786
|
json(res, { file: fp, sessions: rows });
|
|
766
787
|
}
|
|
788
|
+
else if (pathname === '/api/insights') {
|
|
789
|
+
const summary = getInsightsSummary(db);
|
|
790
|
+
return json(res, summary);
|
|
791
|
+
}
|
|
792
|
+
else if (pathname.match(/^\/api\/insights\/session\/[^/]+$/)) {
|
|
793
|
+
const id = pathname.split('/')[4];
|
|
794
|
+
const row = db.prepare('SELECT * FROM session_insights WHERE session_id = ?').get(id);
|
|
795
|
+
if (!row) {
|
|
796
|
+
// Compute on-the-fly if not yet analyzed
|
|
797
|
+
const result = analyzeSession(db, id);
|
|
798
|
+
if (!result) return json(res, { error: 'Session not found' }, 404);
|
|
799
|
+
return json(res, result);
|
|
800
|
+
}
|
|
801
|
+
return json(res, {
|
|
802
|
+
session_id: row.session_id,
|
|
803
|
+
signals: JSON.parse(row.signals || '[]'),
|
|
804
|
+
confusion_score: row.confusion_score,
|
|
805
|
+
flagged: !!row.flagged,
|
|
806
|
+
computed_at: row.computed_at
|
|
807
|
+
});
|
|
808
|
+
}
|
|
767
809
|
else if (!serveStatic(req, res)) {
|
|
768
810
|
const index = path.join(PUBLIC, 'index.html');
|
|
769
811
|
if (fs.existsSync(index)) {
|
package/package.json
CHANGED
package/public/app.js
CHANGED
|
@@ -239,6 +239,16 @@ function updateNavActive(view) {
|
|
|
239
239
|
$$('.nav-item').forEach(i => i.classList.remove('active'));
|
|
240
240
|
const navItem = $(`.nav-item[data-view="${view}"]`);
|
|
241
241
|
if (navItem) navItem.classList.add('active');
|
|
242
|
+
// Settings gear buttons
|
|
243
|
+
const isSettings = view === 'stats';
|
|
244
|
+
document.getElementById('settings-btn')?.classList.toggle('active', isSettings);
|
|
245
|
+
document.getElementById('settings-btn-mobile')?.classList.toggle('active', isSettings);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
function updateMobileNavActive(view) {
|
|
249
|
+
$$('.mobile-nav-btn').forEach(b => b.classList.remove('active'));
|
|
250
|
+
const btn = $(`.mobile-nav-btn[data-view="${view}"]`);
|
|
251
|
+
if (btn) btn.classList.add('active');
|
|
242
252
|
}
|
|
243
253
|
|
|
244
254
|
function handleRoute() {
|
|
@@ -253,13 +263,15 @@ function handleRoute() {
|
|
|
253
263
|
}
|
|
254
264
|
|
|
255
265
|
const normalized = raw === 'search' ? 'overview' : raw;
|
|
256
|
-
const view = normalized === 'overview' || normalized === 'sessions' || normalized === 'timeline' || normalized === 'files' || normalized === 'stats' ? normalized : 'overview';
|
|
266
|
+
const view = normalized === 'overview' || normalized === 'sessions' || normalized === 'timeline' || normalized === 'files' || normalized === 'stats' || normalized === 'insights' ? normalized : 'overview';
|
|
257
267
|
window._lastView = view;
|
|
258
268
|
updateNavActive(view);
|
|
269
|
+
updateMobileNavActive(view);
|
|
259
270
|
if (view === 'overview') viewOverview();
|
|
260
271
|
else if (view === 'sessions') viewSessions();
|
|
261
272
|
else if (view === 'files') viewFiles();
|
|
262
273
|
else if (view === 'timeline') viewTimeline();
|
|
274
|
+
else if (view === 'insights') viewInsights();
|
|
263
275
|
else viewStats();
|
|
264
276
|
}
|
|
265
277
|
|
|
@@ -690,6 +702,7 @@ async function viewSession(id) {
|
|
|
690
702
|
<div class="section-label" id="sessionEventsLabel">Events</div>
|
|
691
703
|
<div id="eventsContainer"></div>
|
|
692
704
|
<div class="empty" id="sessionEventsEmpty" style="display:none"><h2>No events</h2><p>This session has no events to display.</p></div>
|
|
705
|
+
<div id="sessionInsightsPanel" class="loading" style="margin-top:var(--space-xl)">Loading insights...</div>
|
|
693
706
|
`;
|
|
694
707
|
|
|
695
708
|
const PAGE_SIZE = 50;
|
|
@@ -1018,6 +1031,12 @@ async function viewSession(id) {
|
|
|
1018
1031
|
const ind = document.getElementById('newEventsIndicator');
|
|
1019
1032
|
if (ind) ind.remove();
|
|
1020
1033
|
};
|
|
1034
|
+
|
|
1035
|
+
// Load insights panel
|
|
1036
|
+
api(`/insights/session/${id}`).then(insights => {
|
|
1037
|
+
const panel = document.getElementById('sessionInsightsPanel');
|
|
1038
|
+
if (panel) panel.outerHTML = renderInsightsPanel(insights._error ? null : insights);
|
|
1039
|
+
});
|
|
1021
1040
|
}
|
|
1022
1041
|
|
|
1023
1042
|
async function viewTimeline(date) {
|
|
@@ -1196,7 +1215,7 @@ async function viewStats() {
|
|
|
1196
1215
|
<div class="config-card"><div class="config-label">Storage Mode</div><div class="config-value">${escHtml(data.storageMode || 'reference')}</div></div>
|
|
1197
1216
|
<div class="config-card"><div class="config-label">DB Size</div><div class="config-value" id="dbSizeValue">${escHtml(data.dbSize?.display || 'N/A')}</div></div>
|
|
1198
1217
|
</div>
|
|
1199
|
-
<p class="settings-help" style="margin-bottom:var(--space-sm)">Date range: ${fmtDate(data.dateRange?.earliest)}
|
|
1218
|
+
<p class="settings-help" style="margin-bottom:var(--space-sm)">Date range: ${fmtDate(data.dateRange?.earliest)} to ${fmtDate(data.dateRange?.latest)}</p>
|
|
1200
1219
|
<div class="settings-maintenance">
|
|
1201
1220
|
<button class="export-btn" id="optimizeDbBtn">Optimize Database</button>
|
|
1202
1221
|
<span id="optimizeDbStatus" class="settings-maintenance-status"></span>
|
|
@@ -1524,6 +1543,166 @@ async function viewFileDetail(filePath) {
|
|
|
1524
1543
|
});
|
|
1525
1544
|
}
|
|
1526
1545
|
|
|
1546
|
+
// --- Insights helpers ---
|
|
1547
|
+
const SIGNAL_LABELS = {
|
|
1548
|
+
tool_retry_loop: 'Repeated Actions',
|
|
1549
|
+
session_bail: 'No Output Produced',
|
|
1550
|
+
high_error_rate: 'Frequent Errors',
|
|
1551
|
+
long_prompt_short_session: 'Vague Instructions',
|
|
1552
|
+
no_completion: 'Incomplete Session'
|
|
1553
|
+
};
|
|
1554
|
+
|
|
1555
|
+
const SIGNAL_DESCRIPTIONS = {
|
|
1556
|
+
tool_retry_loop: 'The agent called the same tool many times in a row, suggesting it was stuck in a retry loop',
|
|
1557
|
+
session_bail: 'The agent ran many actions but never wrote or edited any files',
|
|
1558
|
+
high_error_rate: 'More than 30% of tool calls returned errors',
|
|
1559
|
+
long_prompt_short_session: 'A very short prompt led to a long session, suggesting the agent lacked sufficient context',
|
|
1560
|
+
no_completion: 'The session ended mid-action instead of finishing with a response'
|
|
1561
|
+
};
|
|
1562
|
+
|
|
1563
|
+
const SIGNAL_COLORS = {
|
|
1564
|
+
tool_retry_loop: 'amber',
|
|
1565
|
+
high_error_rate: 'red',
|
|
1566
|
+
no_completion: 'purple',
|
|
1567
|
+
session_bail: 'teal',
|
|
1568
|
+
long_prompt_short_session: 'accent'
|
|
1569
|
+
};
|
|
1570
|
+
|
|
1571
|
+
function renderSignalTag(sig) {
|
|
1572
|
+
const label = SIGNAL_LABELS[sig.type] || sig.type;
|
|
1573
|
+
const color = SIGNAL_COLORS[sig.type] || 'muted';
|
|
1574
|
+
const desc = SIGNAL_DESCRIPTIONS[sig.type] || '';
|
|
1575
|
+
return `<span class="signal-tag signal-${color}"${desc ? ` title="${escHtml(desc)}"` : ''}><span class="signal-dot"></span>${escHtml(label)}</span>`;
|
|
1576
|
+
}
|
|
1577
|
+
|
|
1578
|
+
function renderReliabilityBadge(score) {
|
|
1579
|
+
const reliability = 100 - score;
|
|
1580
|
+
return `<span class="insight-score-value" title="Reliability score: higher means fewer errors">${reliability}</span>`;
|
|
1581
|
+
}
|
|
1582
|
+
|
|
1583
|
+
function renderIssueRateBadge(signals) {
|
|
1584
|
+
const TOTAL_SIGNAL_TYPES = 5;
|
|
1585
|
+
const uniqueTypes = new Set((signals || []).map(s => s.type)).size;
|
|
1586
|
+
const rate = Math.round((uniqueTypes / TOTAL_SIGNAL_TYPES) * 100);
|
|
1587
|
+
return `<span class="insight-score-value" title="${uniqueTypes} of ${TOTAL_SIGNAL_TYPES} issue types detected">${rate}%</span>`;
|
|
1588
|
+
}
|
|
1589
|
+
|
|
1590
|
+
function renderInsightsPanel(insights) {
|
|
1591
|
+
if (!insights || !insights.signals || !insights.signals.length) {
|
|
1592
|
+
return `<div class="insights-panel insights-clean"><span style="color:var(--text-tertiary);font-size:13px">No issues detected</span></div>`;
|
|
1593
|
+
}
|
|
1594
|
+
return `<div class="insights-panel">
|
|
1595
|
+
<div class="section-label" style="margin-top:0">Session Health ${renderReliabilityBadge(insights.confusion_score)}</div>
|
|
1596
|
+
<div class="insights-signals">
|
|
1597
|
+
${insights.signals.map(sig => {
|
|
1598
|
+
let detail = '';
|
|
1599
|
+
if (sig.type === 'tool_retry_loop') detail = `${escHtml(sig.tool)} called ${sig.count}x consecutively`;
|
|
1600
|
+
else if (sig.type === 'session_bail') detail = `${sig.tool_calls} tool calls with no file writes`;
|
|
1601
|
+
else if (sig.type === 'high_error_rate') detail = `${sig.rate}% error rate (${sig.error_count}/${sig.total})`;
|
|
1602
|
+
else if (sig.type === 'long_prompt_short_session') detail = `${sig.prompt_words} word prompt, ${sig.tool_calls} tool calls`;
|
|
1603
|
+
else if (sig.type === 'no_completion') detail = `Ended on ${escHtml(sig.last_event_type)}${sig.last_tool ? ': ' + escHtml(sig.last_tool) : ''}`;
|
|
1604
|
+
return `<div class="insight-callout">
|
|
1605
|
+
${renderSignalTag(sig)}
|
|
1606
|
+
<span class="insight-detail">${detail}</span>
|
|
1607
|
+
</div>`;
|
|
1608
|
+
}).join('')}
|
|
1609
|
+
</div>
|
|
1610
|
+
</div>`;
|
|
1611
|
+
}
|
|
1612
|
+
|
|
1613
|
+
async function viewInsights() {
|
|
1614
|
+
clearJumpUi();
|
|
1615
|
+
window._lastView = 'insights';
|
|
1616
|
+
content.innerHTML = `<div class="page-title">Insights</div><div class="stat-grid">${skeletonRows(3, 'stats')}</div>`;
|
|
1617
|
+
transitionView();
|
|
1618
|
+
|
|
1619
|
+
const data = await api('/insights');
|
|
1620
|
+
if (data._error) {
|
|
1621
|
+
content.innerHTML = '<div class="empty"><h2>Unable to load</h2><p>Server unavailable. Pull to refresh or try again.</p></div>';
|
|
1622
|
+
return;
|
|
1623
|
+
}
|
|
1624
|
+
|
|
1625
|
+
// Signal breakdown data — sorted by count descending
|
|
1626
|
+
const signalTypes = Object.keys(SIGNAL_LABELS).sort((a, b) => (data.signal_counts[b] || 0) - (data.signal_counts[a] || 0));
|
|
1627
|
+
const maxSignalCount = Math.max(1, ...signalTypes.map(t => data.signal_counts[t] || 0));
|
|
1628
|
+
|
|
1629
|
+
// Issue rate
|
|
1630
|
+
const issueRate = data.total_sessions > 0 ? Math.round((data.flagged_count / data.total_sessions) * 100) : 0;
|
|
1631
|
+
|
|
1632
|
+
// Reliability score (inverted struggle score)
|
|
1633
|
+
const reliabilityScore = 100 - (data.avg_confusion_score || 0);
|
|
1634
|
+
|
|
1635
|
+
let html = `<div class="page-title">Insights</div>
|
|
1636
|
+
|
|
1637
|
+
<div class="stat-grid">
|
|
1638
|
+
<div class="stat-card accent-amber"><div class="label">Issue Rate</div><div class="value">${issueRate}%</div><div class="stat-desc">${data.flagged_count} of ${data.total_sessions} sessions had at least one detected issue</div></div>
|
|
1639
|
+
<div class="stat-card accent-purple"><div class="label">Reliability Score</div><div class="value">${reliabilityScore}</div><div class="stat-desc">How often the agent completed tasks cleanly, out of 100</div></div>
|
|
1640
|
+
</div>
|
|
1641
|
+
|
|
1642
|
+
<div class="section-label">Issue Types</div>
|
|
1643
|
+
<div class="signal-chart">
|
|
1644
|
+
${signalTypes.map(type => {
|
|
1645
|
+
const count = data.signal_counts[type] || 0;
|
|
1646
|
+
const pct = Math.round((count / maxSignalCount) * 100);
|
|
1647
|
+
const color = SIGNAL_COLORS[type] || 'muted';
|
|
1648
|
+
const desc = SIGNAL_DESCRIPTIONS[type] || '';
|
|
1649
|
+
const barColor = color === 'muted' ? 'var(--text-tertiary)' : `var(--${color})`;
|
|
1650
|
+
return `<div class="signal-lollipop-row${desc ? ' signal-lollipop-expandable' : ''}" data-desc="${escHtml(desc)}">
|
|
1651
|
+
<div class="signal-lollipop-main">
|
|
1652
|
+
<span class="signal-bar-label">${SIGNAL_LABELS[type]}</span>
|
|
1653
|
+
<div class="signal-lollipop-track">
|
|
1654
|
+
<svg width="100%" height="20" class="signal-lollipop-svg">
|
|
1655
|
+
<line x1="0" y1="10" x2="${pct}%" y2="10" stroke="${barColor}" stroke-width="2"/>
|
|
1656
|
+
<circle cx="${pct}%" cy="10" r="5" fill="${barColor}"/>
|
|
1657
|
+
</svg>
|
|
1658
|
+
</div>
|
|
1659
|
+
<span class="signal-bar-count">${count}</span>
|
|
1660
|
+
</div>
|
|
1661
|
+
${desc ? `<div class="signal-lollipop-desc">${escHtml(desc)}</div>` : ''}
|
|
1662
|
+
</div>`;
|
|
1663
|
+
}).join('')}
|
|
1664
|
+
</div>
|
|
1665
|
+
|
|
1666
|
+
<div class="section-label">Sessions with Issues (${data.flagged_count})</div>
|
|
1667
|
+
<div id="insightsList">
|
|
1668
|
+
${data.top_flagged.length ? [...data.top_flagged].sort((a, b) => {
|
|
1669
|
+
const aRate = new Set((a.signals||[]).map(s=>s.type)).size;
|
|
1670
|
+
const bRate = new Set((b.signals||[]).map(s=>s.type)).size;
|
|
1671
|
+
return bRate - aRate || b.confusion_score - a.confusion_score;
|
|
1672
|
+
}).map(s => {
|
|
1673
|
+
const summary = cleanSessionSummary(s.summary, '');
|
|
1674
|
+
return `<div class="session-item insight-row" data-id="${escHtml(s.session_id)}">
|
|
1675
|
+
<div class="session-header">
|
|
1676
|
+
<span class="session-time">${fmtTime(s.start_time)}</span>
|
|
1677
|
+
<span class="insight-scores">issue rate ${renderIssueRateBadge(s.signals)} · reliability ${renderReliabilityBadge(s.confusion_score)}</span>
|
|
1678
|
+
</div>
|
|
1679
|
+
<div class="session-summary">${escHtml(truncate(summary, 120))}</div>
|
|
1680
|
+
<div class="session-meta">
|
|
1681
|
+
<span style="display:flex;gap:6px;align-items:center;flex-wrap:wrap">
|
|
1682
|
+
${s.agent ? `<span class="session-agent">${escHtml(normalizeAgentLabel(s.agent))}</span>` : ''}
|
|
1683
|
+
${s.model ? `<span class="session-model">${escHtml(s.model)}</span>` : ''}
|
|
1684
|
+
</span>
|
|
1685
|
+
<span class="insight-signal-tags">${s.signals.map(sig => renderSignalTag(sig)).join('')}</span>
|
|
1686
|
+
</div>
|
|
1687
|
+
</div>`;
|
|
1688
|
+
}).join('') : '<div class="empty"><p>No flagged sessions found</p></div>'}
|
|
1689
|
+
</div>
|
|
1690
|
+
`;
|
|
1691
|
+
|
|
1692
|
+
content.innerHTML = html;
|
|
1693
|
+
transitionView();
|
|
1694
|
+
|
|
1695
|
+
$$('.session-item', content).forEach(item => {
|
|
1696
|
+
item.addEventListener('click', () => viewSession(item.dataset.id));
|
|
1697
|
+
});
|
|
1698
|
+
|
|
1699
|
+
$$('.signal-lollipop-expandable', content).forEach(row => {
|
|
1700
|
+
row.addEventListener('click', () => {
|
|
1701
|
+
row.classList.toggle('signal-lollipop-open');
|
|
1702
|
+
});
|
|
1703
|
+
});
|
|
1704
|
+
}
|
|
1705
|
+
|
|
1527
1706
|
// --- Navigation ---
|
|
1528
1707
|
window._searchType = '';
|
|
1529
1708
|
window._searchRole = '';
|
|
@@ -1542,6 +1721,7 @@ $$('.nav-item').forEach(item => {
|
|
|
1542
1721
|
else if (view === 'sessions') viewSessions();
|
|
1543
1722
|
else if (view === 'files') viewFiles();
|
|
1544
1723
|
else if (view === 'timeline') viewTimeline();
|
|
1724
|
+
else if (view === 'insights') viewInsights();
|
|
1545
1725
|
else if (view === 'stats') viewStats();
|
|
1546
1726
|
});
|
|
1547
1727
|
});
|
|
@@ -1788,8 +1968,14 @@ function openCmdk() {
|
|
|
1788
1968
|
initTheme();
|
|
1789
1969
|
document.getElementById('theme-toggle')?.addEventListener('click', toggleTheme);
|
|
1790
1970
|
document.getElementById('theme-toggle-mobile')?.addEventListener('click', toggleTheme);
|
|
1791
|
-
document.getElementById('cmdkBtn')?.addEventListener('click', () => openCmdk());
|
|
1792
1971
|
document.getElementById('mobile-search-btn')?.addEventListener('click', () => openCmdk());
|
|
1972
|
+
document.getElementById('settings-btn')?.addEventListener('click', () => { window._lastView = 'stats'; updateNavActive('stats'); setHash('stats'); viewStats(); });
|
|
1973
|
+
document.getElementById('settings-btn-mobile')?.addEventListener('click', () => { window._lastView = 'stats'; updateNavActive('stats'); setHash('stats'); viewStats(); });
|
|
1974
|
+
document.getElementById('cmdkBtn')?.addEventListener('click', () => openCmdk());
|
|
1975
|
+
|
|
1976
|
+
|
|
1977
|
+
|
|
1978
|
+
|
|
1793
1979
|
document.addEventListener('keydown', e => {
|
|
1794
1980
|
if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === 'k') {
|
|
1795
1981
|
e.preventDefault();
|
package/public/index.html
CHANGED
|
@@ -21,7 +21,10 @@
|
|
|
21
21
|
<nav class="sidebar">
|
|
22
22
|
<div class="sidebar-header">
|
|
23
23
|
<h1>Agent<span>Acta</span></h1>
|
|
24
|
-
<
|
|
24
|
+
<div class="sidebar-header-actions">
|
|
25
|
+
<button class="theme-toggle" id="theme-toggle" title="Toggle theme" aria-label="Toggle theme"><svg class="theme-icon-sun" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="5"/><path d="M12 1v2M12 21v2M4.22 4.22l1.42 1.42M18.36 18.36l1.42 1.42M1 12h2M21 12h2M4.22 19.78l1.42-1.42M18.36 5.64l1.42-1.42"/></svg><svg class="theme-icon-moon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/></svg></button>
|
|
26
|
+
<button class="theme-toggle" id="settings-btn" title="Settings" aria-label="Settings"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M10.325 4.317a1.724 1.724 0 0 1 3.35 0 1.724 1.724 0 0 0 2.573 1.066 1.724 1.724 0 0 1 2.28 2.28 1.724 1.724 0 0 0 1.065 2.573 1.724 1.724 0 0 1 0 3.35 1.724 1.724 0 0 0-1.066 2.573 1.724 1.724 0 0 1-2.28 2.28 1.724 1.724 0 0 0-2.573 1.065 1.724 1.724 0 0 1-3.35 0 1.724 1.724 0 0 0-2.573-1.066 1.724 1.724 0 0 1-2.28-2.28 1.724 1.724 0 0 0-1.065-2.573 1.724 1.724 0 0 1 0-3.35 1.724 1.724 0 0 0 1.066-2.573 1.724 1.724 0 0 1 2.28-2.28 1.724 1.724 0 0 0 2.572-1.065z"/><circle cx="12" cy="12" r="3"/></svg></button>
|
|
27
|
+
</div>
|
|
25
28
|
</div>
|
|
26
29
|
<button class="cmdk-trigger" id="cmdkBtn" aria-label="Search" title="Search (⌘K)">
|
|
27
30
|
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><path d="m21 21-4.35-4.35"/></svg>
|
|
@@ -29,26 +32,26 @@
|
|
|
29
32
|
<kbd>⌘K</kbd>
|
|
30
33
|
</button>
|
|
31
34
|
<div class="nav-section">
|
|
35
|
+
<div class="nav-item active" data-view="overview">
|
|
36
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M3 12h7V3H3z"/><path d="M14 21h7v-7h-7z"/><path d="M14 10h7V3h-7z"/><path d="M3 21h7v-5H3z"/></svg>
|
|
37
|
+
<span>Overview</span>
|
|
38
|
+
</div>
|
|
32
39
|
<div class="nav-item" data-view="sessions">
|
|
33
40
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg>
|
|
34
41
|
<span>Sessions</span>
|
|
35
42
|
</div>
|
|
43
|
+
<div class="nav-item" data-view="insights">
|
|
44
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="22 12 18 12 15 21 9 3 6 12 2 12"/></svg>
|
|
45
|
+
<span>Insights</span>
|
|
46
|
+
</div>
|
|
36
47
|
<div class="nav-item" data-view="timeline">
|
|
37
48
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg>
|
|
38
49
|
<span>Timeline</span>
|
|
39
50
|
</div>
|
|
40
|
-
<div class="nav-item active" data-view="overview">
|
|
41
|
-
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M3 12h7V3H3z"/><path d="M14 21h7v-7h-7z"/><path d="M14 10h7V3h-7z"/><path d="M3 21h7v-5H3z"/></svg>
|
|
42
|
-
<span>Overview</span>
|
|
43
|
-
</div>
|
|
44
51
|
<div class="nav-item" data-view="files">
|
|
45
52
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/></svg>
|
|
46
53
|
<span>Files</span>
|
|
47
54
|
</div>
|
|
48
|
-
<div class="nav-item" data-view="stats">
|
|
49
|
-
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M10.325 4.317a1.724 1.724 0 0 1 3.35 0 1.724 1.724 0 0 0 2.573 1.066 1.724 1.724 0 0 1 2.28 2.28 1.724 1.724 0 0 0 1.065 2.573 1.724 1.724 0 0 1 0 3.35 1.724 1.724 0 0 0-1.066 2.573 1.724 1.724 0 0 1-2.28 2.28 1.724 1.724 0 0 0-2.573 1.065 1.724 1.724 0 0 1-3.35 0 1.724 1.724 0 0 0-2.573-1.066 1.724 1.724 0 0 1-2.28-2.28 1.724 1.724 0 0 0-1.065-2.573 1.724 1.724 0 0 1 0-3.35 1.724 1.724 0 0 0 1.066-2.573 1.724 1.724 0 0 1 2.28-2.28 1.724 1.724 0 0 0 2.572-1.065z"/><circle cx="12" cy="12" r="3"/></svg>
|
|
50
|
-
<span>Settings</span>
|
|
51
|
-
</div>
|
|
52
55
|
|
|
53
56
|
</div>
|
|
54
57
|
</nav>
|
|
@@ -56,8 +59,8 @@
|
|
|
56
59
|
<div class="mobile-toolbar">
|
|
57
60
|
<button class="mobile-search-btn" id="mobile-search-btn" title="Search" aria-label="Search"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><path d="m21 21-4.35-4.35"/></svg></button>
|
|
58
61
|
<button class="theme-toggle-mobile" id="theme-toggle-mobile" title="Toggle theme" aria-label="Toggle theme"><svg class="theme-icon-sun" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="5"/><path d="M12 1v2M12 21v2M4.22 4.22l1.42 1.42M18.36 18.36l1.42 1.42M1 12h2M21 12h2M4.22 19.78l1.42-1.42M18.36 5.64l1.42-1.42"/></svg><svg class="theme-icon-moon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/></svg></button>
|
|
62
|
+
<button class="theme-toggle-mobile" id="settings-btn-mobile" title="Settings" aria-label="Settings"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M10.325 4.317a1.724 1.724 0 0 1 3.35 0 1.724 1.724 0 0 0 2.573 1.066 1.724 1.724 0 0 1 2.28 2.28 1.724 1.724 0 0 0 1.065 2.573 1.724 1.724 0 0 1 0 3.35 1.724 1.724 0 0 0-1.066 2.573 1.724 1.724 0 0 1-2.28 2.28 1.724 1.724 0 0 0-2.573 1.065 1.724 1.724 0 0 1-3.35 0 1.724 1.724 0 0 0-2.573-1.066 1.724 1.724 0 0 1-2.28-2.28 1.724 1.724 0 0 0-1.065-2.573 1.724 1.724 0 0 1 0-3.35 1.724 1.724 0 0 0 1.066-2.573 1.724 1.724 0 0 1 2.28-2.28 1.724 1.724 0 0 0 2.572-1.065z"/><circle cx="12" cy="12" r="3"/></svg></button>
|
|
59
63
|
</div>
|
|
60
|
-
|
|
61
64
|
</div>
|
|
62
65
|
<script src="/app.js"></script>
|
|
63
66
|
<script>
|
package/public/style.css
CHANGED
|
@@ -197,7 +197,12 @@ body.cmdk-open {
|
|
|
197
197
|
display: flex;
|
|
198
198
|
align-items: center;
|
|
199
199
|
justify-content: space-between;
|
|
200
|
-
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
.sidebar-header-actions {
|
|
203
|
+
display: flex;
|
|
204
|
+
align-items: center;
|
|
205
|
+
gap: 6px;
|
|
201
206
|
}
|
|
202
207
|
|
|
203
208
|
.sidebar h1 {
|
|
@@ -233,6 +238,13 @@ body.cmdk-open {
|
|
|
233
238
|
color: var(--text-primary);
|
|
234
239
|
}
|
|
235
240
|
|
|
241
|
+
.theme-toggle.active,
|
|
242
|
+
.theme-toggle-mobile.active {
|
|
243
|
+
color: var(--accent);
|
|
244
|
+
border-color: color-mix(in srgb, var(--accent) 40%, transparent);
|
|
245
|
+
background: color-mix(in srgb, var(--accent) 10%, var(--bg-elevated));
|
|
246
|
+
}
|
|
247
|
+
|
|
236
248
|
.theme-toggle svg,
|
|
237
249
|
.theme-toggle-mobile svg {
|
|
238
250
|
width: 16px;
|
|
@@ -605,6 +617,28 @@ body.cmdk-open {
|
|
|
605
617
|
border-radius: 10px;
|
|
606
618
|
}
|
|
607
619
|
|
|
620
|
+
.signal-tag {
|
|
621
|
+
display: inline-flex;
|
|
622
|
+
align-items: center;
|
|
623
|
+
gap: 6px;
|
|
624
|
+
font-size: 11px;
|
|
625
|
+
font-weight: 500;
|
|
626
|
+
color: var(--text-secondary);
|
|
627
|
+
white-space: nowrap;
|
|
628
|
+
}
|
|
629
|
+
.signal-dot {
|
|
630
|
+
width: 7px;
|
|
631
|
+
height: 7px;
|
|
632
|
+
border-radius: 50%;
|
|
633
|
+
flex-shrink: 0;
|
|
634
|
+
}
|
|
635
|
+
.signal-amber .signal-dot { background: var(--amber); }
|
|
636
|
+
.signal-red .signal-dot { background: var(--red); }
|
|
637
|
+
.signal-purple .signal-dot { background: var(--purple); }
|
|
638
|
+
.signal-teal .signal-dot { background: var(--teal); }
|
|
639
|
+
.signal-accent .signal-dot { background: var(--accent); }
|
|
640
|
+
.signal-muted .signal-dot { background: var(--text-tertiary); }
|
|
641
|
+
|
|
608
642
|
.session-type {
|
|
609
643
|
font-size: 11px;
|
|
610
644
|
font-weight: 500;
|
|
@@ -623,6 +657,8 @@ body.cmdk-open {
|
|
|
623
657
|
|
|
624
658
|
.session-meta {
|
|
625
659
|
display: flex;
|
|
660
|
+
align-items: center;
|
|
661
|
+
justify-content: space-between;
|
|
626
662
|
gap: var(--space-lg);
|
|
627
663
|
margin-top: var(--space-sm);
|
|
628
664
|
font-size: 12px;
|
|
@@ -1511,22 +1547,25 @@ mark {
|
|
|
1511
1547
|
.sidebar {
|
|
1512
1548
|
position: fixed;
|
|
1513
1549
|
top: auto;
|
|
1514
|
-
bottom:
|
|
1515
|
-
left:
|
|
1516
|
-
right:
|
|
1517
|
-
|
|
1550
|
+
bottom: calc(24px + env(safe-area-inset-bottom, 0px));
|
|
1551
|
+
left: 50%;
|
|
1552
|
+
right: auto;
|
|
1553
|
+
transform: translateX(-50%);
|
|
1554
|
+
width: auto;
|
|
1518
1555
|
height: auto;
|
|
1519
1556
|
border-right: none;
|
|
1520
|
-
border
|
|
1521
|
-
|
|
1522
|
-
padding
|
|
1557
|
+
border: 1px solid var(--border-default);
|
|
1558
|
+
border-radius: 999px;
|
|
1559
|
+
padding: 8px 12px;
|
|
1523
1560
|
display: flex;
|
|
1524
1561
|
flex-direction: row;
|
|
1525
|
-
|
|
1562
|
+
align-items: center;
|
|
1563
|
+
overflow: visible;
|
|
1526
1564
|
z-index: 100;
|
|
1527
1565
|
background: var(--bg-surface);
|
|
1528
1566
|
backdrop-filter: blur(20px);
|
|
1529
1567
|
-webkit-backdrop-filter: blur(20px);
|
|
1568
|
+
box-shadow: 0 8px 32px rgba(0,0,0,0.28);
|
|
1530
1569
|
}
|
|
1531
1570
|
|
|
1532
1571
|
.sidebar-header { display: none; }
|
|
@@ -1535,29 +1574,30 @@ mark {
|
|
|
1535
1574
|
display: flex;
|
|
1536
1575
|
}
|
|
1537
1576
|
|
|
1577
|
+
.sidebar-header, .cmdk-trigger { display: none; }
|
|
1578
|
+
|
|
1538
1579
|
.nav-section {
|
|
1539
1580
|
display: flex;
|
|
1540
1581
|
flex-direction: row;
|
|
1541
|
-
width: 100%;
|
|
1542
1582
|
padding: 0;
|
|
1543
|
-
gap:
|
|
1583
|
+
gap: 2px;
|
|
1544
1584
|
}
|
|
1545
1585
|
|
|
1546
1586
|
.nav-item {
|
|
1547
|
-
flex:
|
|
1587
|
+
flex: none;
|
|
1548
1588
|
flex-direction: column;
|
|
1549
1589
|
align-items: center;
|
|
1550
1590
|
justify-content: center;
|
|
1551
|
-
padding:
|
|
1591
|
+
padding: 7px 14px 5px;
|
|
1552
1592
|
font-size: 10px;
|
|
1553
1593
|
gap: 3px;
|
|
1554
|
-
border-radius:
|
|
1594
|
+
border-radius: 999px;
|
|
1555
1595
|
letter-spacing: 0.02em;
|
|
1556
1596
|
}
|
|
1557
1597
|
|
|
1558
1598
|
.nav-item::before { display: none; }
|
|
1559
1599
|
|
|
1560
|
-
.nav-item svg { width:
|
|
1600
|
+
.nav-item svg { width: 20px; height: 20px; }
|
|
1561
1601
|
|
|
1562
1602
|
.nav-item.active {
|
|
1563
1603
|
color: var(--accent);
|
|
@@ -1568,7 +1608,7 @@ mark {
|
|
|
1568
1608
|
margin-left: 0;
|
|
1569
1609
|
padding: var(--space-xl) var(--space-lg);
|
|
1570
1610
|
padding-top: calc(var(--space-xl) + env(safe-area-inset-top, 0px));
|
|
1571
|
-
padding-bottom: calc(
|
|
1611
|
+
padding-bottom: calc(100px + max(env(safe-area-inset-bottom, 0px), 20px));
|
|
1572
1612
|
max-width: 100%;
|
|
1573
1613
|
}
|
|
1574
1614
|
|
|
@@ -1911,3 +1951,144 @@ mark {
|
|
|
1911
1951
|
.cmdk-item-sub { display: none; }
|
|
1912
1952
|
.cmdk-item-meta { font-size: 10px; }
|
|
1913
1953
|
}
|
|
1954
|
+
|
|
1955
|
+
/* ── Insights ── */
|
|
1956
|
+
.settings-gear {
|
|
1957
|
+
background: none;
|
|
1958
|
+
border: none;
|
|
1959
|
+
cursor: pointer;
|
|
1960
|
+
padding: 4px;
|
|
1961
|
+
border-radius: var(--radius-sm);
|
|
1962
|
+
color: var(--text-muted);
|
|
1963
|
+
display: flex;
|
|
1964
|
+
align-items: center;
|
|
1965
|
+
justify-content: center;
|
|
1966
|
+
transition: color 0.15s, background 0.15s;
|
|
1967
|
+
}
|
|
1968
|
+
.settings-gear svg { width: 16px; height: 16px; }
|
|
1969
|
+
.settings-gear:hover { color: var(--text-primary); background: var(--hover-bg); }
|
|
1970
|
+
|
|
1971
|
+
.signal-lollipop-row {
|
|
1972
|
+
display: flex;
|
|
1973
|
+
flex-direction: column;
|
|
1974
|
+
margin-bottom: 8px;
|
|
1975
|
+
}
|
|
1976
|
+
.signal-lollipop-expandable {
|
|
1977
|
+
cursor: pointer;
|
|
1978
|
+
}
|
|
1979
|
+
.signal-lollipop-main {
|
|
1980
|
+
display: flex;
|
|
1981
|
+
align-items: center;
|
|
1982
|
+
gap: 10px;
|
|
1983
|
+
}
|
|
1984
|
+
.signal-bar-label {
|
|
1985
|
+
font-size: 13px;
|
|
1986
|
+
color: var(--text-secondary);
|
|
1987
|
+
width: 130px;
|
|
1988
|
+
flex-shrink: 0;
|
|
1989
|
+
}
|
|
1990
|
+
.signal-expand-hint {
|
|
1991
|
+
font-size: 11px;
|
|
1992
|
+
color: var(--text-tertiary);
|
|
1993
|
+
flex-shrink: 0;
|
|
1994
|
+
}
|
|
1995
|
+
.signal-lollipop-desc {
|
|
1996
|
+
display: none;
|
|
1997
|
+
font-size: 12px;
|
|
1998
|
+
color: var(--text-tertiary);
|
|
1999
|
+
padding: 5px 0 3px 0;
|
|
2000
|
+
line-height: 1.4;
|
|
2001
|
+
}
|
|
2002
|
+
.signal-lollipop-open .signal-lollipop-desc {
|
|
2003
|
+
display: block;
|
|
2004
|
+
}
|
|
2005
|
+
.signal-lollipop-open .signal-expand-hint {
|
|
2006
|
+
color: var(--text-secondary);
|
|
2007
|
+
}
|
|
2008
|
+
.signal-lollipop-track {
|
|
2009
|
+
flex: 1;
|
|
2010
|
+
height: 20px;
|
|
2011
|
+
}
|
|
2012
|
+
.signal-lollipop-svg {
|
|
2013
|
+
display: block;
|
|
2014
|
+
overflow: visible;
|
|
2015
|
+
}
|
|
2016
|
+
.signal-bar-count {
|
|
2017
|
+
font-size: 12px;
|
|
2018
|
+
font-variant-numeric: tabular-nums;
|
|
2019
|
+
color: var(--text-muted);
|
|
2020
|
+
width: 36px;
|
|
2021
|
+
text-align: right;
|
|
2022
|
+
flex-shrink: 0;
|
|
2023
|
+
}
|
|
2024
|
+
.stat-desc {
|
|
2025
|
+
font-size: 11px;
|
|
2026
|
+
color: var(--text-tertiary);
|
|
2027
|
+
margin-top: 4px;
|
|
2028
|
+
line-height: 1.3;
|
|
2029
|
+
}
|
|
2030
|
+
|
|
2031
|
+
.insight-row { cursor: pointer; }
|
|
2032
|
+
.insight-row:hover { background: var(--hover-bg); }
|
|
2033
|
+
.insight-callout {
|
|
2034
|
+
display: flex;
|
|
2035
|
+
align-items: flex-start;
|
|
2036
|
+
gap: 8px;
|
|
2037
|
+
padding: 8px 0;
|
|
2038
|
+
border-bottom: 1px solid var(--border-color);
|
|
2039
|
+
font-size: 13px;
|
|
2040
|
+
}
|
|
2041
|
+
.insight-callout:last-child { border-bottom: none; }
|
|
2042
|
+
.insight-detail { color: var(--text-secondary); }
|
|
2043
|
+
.insights-signals { margin-top: 8px; }
|
|
2044
|
+
|
|
2045
|
+
.confusion-badge {
|
|
2046
|
+
display: inline-flex;
|
|
2047
|
+
align-items: center;
|
|
2048
|
+
gap: 4px;
|
|
2049
|
+
padding: 1px 7px;
|
|
2050
|
+
border-radius: 999px;
|
|
2051
|
+
font-size: 11px;
|
|
2052
|
+
font-weight: 600;
|
|
2053
|
+
letter-spacing: 0.02em;
|
|
2054
|
+
}
|
|
2055
|
+
|
|
2056
|
+
.confusion-label {
|
|
2057
|
+
font-weight: 400;
|
|
2058
|
+
font-size: 10px;
|
|
2059
|
+
opacity: 0.7;
|
|
2060
|
+
text-transform: uppercase;
|
|
2061
|
+
letter-spacing: 0.04em;
|
|
2062
|
+
}
|
|
2063
|
+
|
|
2064
|
+
.confusion-issue-count {
|
|
2065
|
+
background: color-mix(in srgb, var(--text-tertiary) 15%, transparent);
|
|
2066
|
+
color: var(--text-secondary);
|
|
2067
|
+
}
|
|
2068
|
+
|
|
2069
|
+
.insight-scores {
|
|
2070
|
+
font-size: 11px;
|
|
2071
|
+
color: var(--text-tertiary);
|
|
2072
|
+
white-space: nowrap;
|
|
2073
|
+
text-transform: uppercase;
|
|
2074
|
+
letter-spacing: 0.05em;
|
|
2075
|
+
}
|
|
2076
|
+
|
|
2077
|
+
.insight-score-value {
|
|
2078
|
+
font-weight: 600;
|
|
2079
|
+
color: var(--text-secondary);
|
|
2080
|
+
}
|
|
2081
|
+
|
|
2082
|
+
.insight-signal-tags {
|
|
2083
|
+
display: flex;
|
|
2084
|
+
flex-wrap: wrap;
|
|
2085
|
+
gap: 5px;
|
|
2086
|
+
align-items: center;
|
|
2087
|
+
}
|
|
2088
|
+
.confusion-green { background: #dcfce7; color: #166534; }
|
|
2089
|
+
.confusion-yellow { background: #fef9c3; color: #854d0e; }
|
|
2090
|
+
.confusion-red { background: #fee2e2; color: #991b1b; }
|
|
2091
|
+
[data-theme="dark"] .confusion-green { background: #14532d; color: #86efac; }
|
|
2092
|
+
[data-theme="dark"] .confusion-yellow { background: #713f12; color: #fde68a; }
|
|
2093
|
+
[data-theme="dark"] .confusion-red { background: #7f1d1d; color: #fca5a5; }
|
|
2094
|
+
|