@co0ontty/wand 1.3.3 → 1.3.6
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/dist/claude-pty-bridge.d.ts +32 -0
- package/dist/claude-pty-bridge.js +186 -8
- package/dist/process-manager.js +87 -190
- package/dist/pty-text-utils.d.ts +12 -0
- package/dist/pty-text-utils.js +37 -1
- package/dist/server-session-routes.d.ts +1 -0
- package/dist/server-session-routes.js +1 -3
- package/dist/server.js +6 -31
- package/dist/session-lifecycle.js +0 -5
- package/dist/session-logger.d.ts +10 -0
- package/dist/types.d.ts +7 -0
- package/dist/web-ui/content/scripts.js +134 -30
- package/dist/web-ui/content/styles.css +111 -2
- package/package.json +1 -1
|
@@ -173,6 +173,12 @@
|
|
|
173
173
|
return (state.config && state.config.defaultCwd) || "/tmp";
|
|
174
174
|
}
|
|
175
175
|
|
|
176
|
+
function resetChatRenderCache() {
|
|
177
|
+
state.lastRenderedHash = 0;
|
|
178
|
+
state.lastRenderedMsgCount = 0;
|
|
179
|
+
state.lastRenderedEmpty = null;
|
|
180
|
+
}
|
|
181
|
+
|
|
176
182
|
function getEffectiveCwd() {
|
|
177
183
|
return state.workingDir || getConfigCwd();
|
|
178
184
|
}
|
|
@@ -324,9 +330,7 @@
|
|
|
324
330
|
|
|
325
331
|
app.innerHTML = isLoggedIn ? renderAppShell() : renderLogin();
|
|
326
332
|
// Reset chat render tracking since DOM was fully replaced
|
|
327
|
-
|
|
328
|
-
state.lastRenderedMsgCount = 0;
|
|
329
|
-
state.lastRenderedEmpty = null;
|
|
333
|
+
resetChatRenderCache();
|
|
330
334
|
attachEventListeners();
|
|
331
335
|
updateDrawerState();
|
|
332
336
|
syncComposerModeSelect();
|
|
@@ -367,6 +371,26 @@
|
|
|
367
371
|
'<button class="shortcut-key" data-key="escape" type="button">Esc</button>';
|
|
368
372
|
}
|
|
369
373
|
|
|
374
|
+
function renderApprovalStatsBadge() {
|
|
375
|
+
var selectedSession = state.sessions.find(function(s) { return s.id === state.selectedId; });
|
|
376
|
+
var stats = selectedSession && selectedSession.approvalStats;
|
|
377
|
+
if (!stats || stats.total === 0) return '<span class="approval-stats hidden" id="approval-stats"></span>';
|
|
378
|
+
return '<span class="approval-stats" id="approval-stats">' +
|
|
379
|
+
'<span class="approval-stats-divider"></span>' +
|
|
380
|
+
'<span class="approval-stats-badge" id="approval-stats-badge" title="本次会话自动批准统计">' +
|
|
381
|
+
'<svg class="approval-stats-icon" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/></svg>' +
|
|
382
|
+
'<span class="approval-stats-total">' + stats.total + '</span>' +
|
|
383
|
+
'</span>' +
|
|
384
|
+
'<span class="approval-stats-popup" id="approval-stats-popup">' +
|
|
385
|
+
'<span class="approval-stats-popup-title">自动批准统计</span>' +
|
|
386
|
+
(stats.command > 0 ? '<span class="approval-stats-row"><span class="approval-stats-row-icon">⚡</span><span class="approval-stats-row-label">命令执行</span><span class="approval-stats-row-count">' + stats.command + '</span></span>' : '') +
|
|
387
|
+
(stats.file > 0 ? '<span class="approval-stats-row"><span class="approval-stats-row-icon">📝</span><span class="approval-stats-row-label">文件写入</span><span class="approval-stats-row-count">' + stats.file + '</span></span>' : '') +
|
|
388
|
+
(stats.tool > 0 ? '<span class="approval-stats-row"><span class="approval-stats-row-icon">🔧</span><span class="approval-stats-row-label">其他工具</span><span class="approval-stats-row-count">' + stats.tool + '</span></span>' : '') +
|
|
389
|
+
'<span class="approval-stats-row approval-stats-row-total"><span class="approval-stats-row-icon">∑</span><span class="approval-stats-row-label">合计</span><span class="approval-stats-row-count">' + stats.total + '</span></span>' +
|
|
390
|
+
'</span>' +
|
|
391
|
+
'</span>';
|
|
392
|
+
}
|
|
393
|
+
|
|
370
394
|
function renderInlineKeyboard() {
|
|
371
395
|
if (!state.selectedId) return "";
|
|
372
396
|
var isTerminal = state.currentView === "terminal";
|
|
@@ -462,6 +486,12 @@
|
|
|
462
486
|
'<span class="session-count" id="session-count">' + String(state.sessions.length) + '</span>' +
|
|
463
487
|
'</div>' +
|
|
464
488
|
'<div class="sidebar-header-actions">' +
|
|
489
|
+
'<button id="sidebar-home-btn" class="btn btn-ghost btn-sm" type="button" title="回到首页">' +
|
|
490
|
+
'<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/><polyline points="9 22 9 12 15 12 15 22"/></svg>' +
|
|
491
|
+
'</button>' +
|
|
492
|
+
'<button id="sidebar-refresh-btn" class="btn btn-ghost btn-sm" type="button" title="刷新页面">' +
|
|
493
|
+
'<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="23 4 23 10 17 10"/><path d="M20.49 15a9 9 0 1 1-2.12-9.36L23 10"/></svg>' +
|
|
494
|
+
'</button>' +
|
|
465
495
|
'<button id="close-drawer-button" class="btn btn-ghost btn-sm sidebar-close" type="button" aria-label="关闭菜单">×</button>' +
|
|
466
496
|
'</div>' +
|
|
467
497
|
'</div>' +
|
|
@@ -569,6 +599,7 @@
|
|
|
569
599
|
'<button id="approve-permission-btn" class="btn btn-permission btn-permission-approve" type="button">批准</button>' +
|
|
570
600
|
'<button id="deny-permission-btn" class="btn btn-permission btn-permission-deny" type="button">拒绝</button>' +
|
|
571
601
|
'</span>' +
|
|
602
|
+
renderApprovalStatsBadge() +
|
|
572
603
|
'</div>' +
|
|
573
604
|
'<div class="input-composer-right">' +
|
|
574
605
|
'<span id="queue-counter" class="queue-counter hidden">队列: 0</span>' +
|
|
@@ -1904,6 +1935,18 @@
|
|
|
1904
1935
|
if (drawerBackdrop) drawerBackdrop.addEventListener("click", closeSessionsDrawer);
|
|
1905
1936
|
var closeDrawerBtn = document.getElementById("close-drawer-button");
|
|
1906
1937
|
if (closeDrawerBtn) closeDrawerBtn.addEventListener("click", closeSessionsDrawer);
|
|
1938
|
+
var homeBtn = document.getElementById("sidebar-home-btn");
|
|
1939
|
+
if (homeBtn) homeBtn.addEventListener("click", function() {
|
|
1940
|
+
state.selectedId = null;
|
|
1941
|
+
persistSelectedId();
|
|
1942
|
+
resetChatRenderCache();
|
|
1943
|
+
closeSessionsDrawer();
|
|
1944
|
+
renderApp();
|
|
1945
|
+
});
|
|
1946
|
+
var refreshBtn = document.getElementById("sidebar-refresh-btn");
|
|
1947
|
+
if (refreshBtn) refreshBtn.addEventListener("click", function() {
|
|
1948
|
+
window.location.reload();
|
|
1949
|
+
});
|
|
1907
1950
|
var logoutBtn = document.getElementById("logout-button");
|
|
1908
1951
|
if (logoutBtn) logoutBtn.addEventListener("click", logout);
|
|
1909
1952
|
var settingsBtn = document.getElementById("settings-button");
|
|
@@ -3561,6 +3604,15 @@
|
|
|
3561
3604
|
return fetch("/api/sessions/" + id, { credentials: "same-origin" })
|
|
3562
3605
|
.then(function(res) { return res.json(); })
|
|
3563
3606
|
.then(function(data) {
|
|
3607
|
+
if (data.error) {
|
|
3608
|
+
// Session no longer exists — deselect and refresh list
|
|
3609
|
+
if (state.selectedId === id) {
|
|
3610
|
+
state.selectedId = null;
|
|
3611
|
+
persistSelectedId();
|
|
3612
|
+
}
|
|
3613
|
+
loadSessions();
|
|
3614
|
+
return;
|
|
3615
|
+
}
|
|
3564
3616
|
updateSessionSnapshot(data);
|
|
3565
3617
|
updateShellChrome();
|
|
3566
3618
|
|
|
@@ -3578,9 +3630,7 @@
|
|
|
3578
3630
|
function selectSession(id) {
|
|
3579
3631
|
state.selectedId = id;
|
|
3580
3632
|
persistSelectedId();
|
|
3581
|
-
|
|
3582
|
-
state.lastRenderedMsgCount = 0;
|
|
3583
|
-
state.lastRenderedEmpty = null;
|
|
3633
|
+
resetChatRenderCache();
|
|
3584
3634
|
state.currentMessages = [];
|
|
3585
3635
|
if (chatRenderTimer) { clearTimeout(chatRenderTimer); chatRenderTimer = null; }
|
|
3586
3636
|
// Reset todo progress bar
|
|
@@ -4167,9 +4217,7 @@
|
|
|
4167
4217
|
state.selectedId = data.id;
|
|
4168
4218
|
persistSelectedId();
|
|
4169
4219
|
state.drafts[data.id] = "";
|
|
4170
|
-
|
|
4171
|
-
state.lastRenderedMsgCount = 0;
|
|
4172
|
-
state.lastRenderedEmpty = null;
|
|
4220
|
+
resetChatRenderCache();
|
|
4173
4221
|
return refreshAll();
|
|
4174
4222
|
})
|
|
4175
4223
|
.then(function() { focusInputBox(true); })
|
|
@@ -4212,9 +4260,7 @@
|
|
|
4212
4260
|
state.selectedId = data.id;
|
|
4213
4261
|
persistSelectedId();
|
|
4214
4262
|
state.drafts[data.id] = "";
|
|
4215
|
-
|
|
4216
|
-
state.lastRenderedMsgCount = 0;
|
|
4217
|
-
state.lastRenderedEmpty = null;
|
|
4263
|
+
resetChatRenderCache();
|
|
4218
4264
|
closeSessionModal();
|
|
4219
4265
|
closeSessionsDrawer();
|
|
4220
4266
|
return refreshAll();
|
|
@@ -4644,9 +4690,7 @@
|
|
|
4644
4690
|
state.selectedId = data.id;
|
|
4645
4691
|
persistSelectedId();
|
|
4646
4692
|
state.drafts[data.id] = "";
|
|
4647
|
-
|
|
4648
|
-
state.lastRenderedMsgCount = 0;
|
|
4649
|
-
state.lastRenderedEmpty = null;
|
|
4693
|
+
resetChatRenderCache();
|
|
4650
4694
|
switchToSessionView(data.id);
|
|
4651
4695
|
updateSessionSnapshot(data);
|
|
4652
4696
|
updateSessionsList();
|
|
@@ -4702,9 +4746,7 @@
|
|
|
4702
4746
|
state.selectedId = data.id;
|
|
4703
4747
|
persistSelectedId();
|
|
4704
4748
|
state.drafts[data.id] = "";
|
|
4705
|
-
|
|
4706
|
-
state.lastRenderedMsgCount = 0;
|
|
4707
|
-
state.lastRenderedEmpty = null;
|
|
4749
|
+
resetChatRenderCache();
|
|
4708
4750
|
if (inputBox) inputBox.value = "";
|
|
4709
4751
|
if (welcomeInput) welcomeInput.value = "";
|
|
4710
4752
|
switchToSessionView(data.id);
|
|
@@ -5592,9 +5634,7 @@
|
|
|
5592
5634
|
|
|
5593
5635
|
function activateSession(data) {
|
|
5594
5636
|
if (!data || !data.id) return Promise.resolve();
|
|
5595
|
-
|
|
5596
|
-
state.lastRenderedMsgCount = 0;
|
|
5597
|
-
state.lastRenderedEmpty = null;
|
|
5637
|
+
resetChatRenderCache();
|
|
5598
5638
|
switchToSessionView(data.id);
|
|
5599
5639
|
updateSessionSnapshot(data);
|
|
5600
5640
|
updateSessionsList();
|
|
@@ -5825,11 +5865,26 @@
|
|
|
5825
5865
|
|
|
5826
5866
|
function handleInputBoxBlur() {
|
|
5827
5867
|
resetInputPanelViewportSpacing();
|
|
5828
|
-
// Restore app container height when keyboard closes
|
|
5829
|
-
|
|
5830
|
-
|
|
5831
|
-
|
|
5832
|
-
|
|
5868
|
+
// Restore app container height when keyboard closes.
|
|
5869
|
+
// Use a short delay because on iOS the visualViewport may not
|
|
5870
|
+
// have updated yet at the moment blur fires.
|
|
5871
|
+
setTimeout(function() {
|
|
5872
|
+
var appContainer = document.querySelector('.app-container');
|
|
5873
|
+
if (appContainer) {
|
|
5874
|
+
// Only clear if keyboard is actually closed now
|
|
5875
|
+
var vv = window.visualViewport;
|
|
5876
|
+
if (vv) {
|
|
5877
|
+
var offsetBottom = window.innerHeight - vv.height - vv.offsetTop;
|
|
5878
|
+
if (offsetBottom <= 50) {
|
|
5879
|
+
appContainer.style.height = '';
|
|
5880
|
+
}
|
|
5881
|
+
} else {
|
|
5882
|
+
appContainer.style.height = '';
|
|
5883
|
+
}
|
|
5884
|
+
}
|
|
5885
|
+
// Scroll the window back to top to fix any residual offset
|
|
5886
|
+
window.scrollTo(0, 0);
|
|
5887
|
+
}, 100);
|
|
5833
5888
|
}
|
|
5834
5889
|
|
|
5835
5890
|
function adjustInputBoxSelection(inputBox) {
|
|
@@ -6542,13 +6597,16 @@
|
|
|
6542
6597
|
var isKeyboardOpen = offsetBottom > 50;
|
|
6543
6598
|
var heightChanged = Math.abs(vv.height - lastHeight) > 8;
|
|
6544
6599
|
|
|
6545
|
-
//
|
|
6546
|
-
// because 100dvh does NOT shrink when keyboard
|
|
6600
|
+
// Dynamically resize the app container to match visible viewport.
|
|
6601
|
+
// This is needed because 100dvh does NOT shrink when the keyboard
|
|
6602
|
+
// appears in PWA standalone mode, and on some browsers the layout
|
|
6603
|
+
// viewport doesn't update on keyboard dismiss without this.
|
|
6547
6604
|
var appContainer = document.querySelector('.app-container');
|
|
6548
6605
|
if (appContainer) {
|
|
6549
6606
|
if (isKeyboardOpen) {
|
|
6550
6607
|
appContainer.style.height = vv.height + 'px';
|
|
6551
|
-
} else {
|
|
6608
|
+
} else if (keyboardOpen) {
|
|
6609
|
+
// Keyboard just closed — clear forced height
|
|
6552
6610
|
appContainer.style.height = '';
|
|
6553
6611
|
}
|
|
6554
6612
|
}
|
|
@@ -6571,6 +6629,9 @@
|
|
|
6571
6629
|
}
|
|
6572
6630
|
|
|
6573
6631
|
vv.addEventListener('resize', debouncedUpdate);
|
|
6632
|
+
// Also listen to scroll — on iOS, keyboard dismiss sometimes only
|
|
6633
|
+
// fires a scroll event (viewport scrolls back) without a resize event.
|
|
6634
|
+
vv.addEventListener('scroll', debouncedUpdate);
|
|
6574
6635
|
|
|
6575
6636
|
updateViewport();
|
|
6576
6637
|
}
|
|
@@ -7026,9 +7087,15 @@
|
|
|
7026
7087
|
if (msg.data.permissionBlocked === false) {
|
|
7027
7088
|
statusUpdate.pendingEscalation = null;
|
|
7028
7089
|
}
|
|
7090
|
+
if (msg.data.approvalStats) {
|
|
7091
|
+
statusUpdate.approvalStats = msg.data.approvalStats;
|
|
7092
|
+
}
|
|
7029
7093
|
updateSessionSnapshot(statusUpdate);
|
|
7030
7094
|
if (msg.sessionId === state.selectedId) {
|
|
7031
7095
|
updateTaskDisplay();
|
|
7096
|
+
if (msg.data.approvalStats) {
|
|
7097
|
+
updateApprovalStats();
|
|
7098
|
+
}
|
|
7032
7099
|
}
|
|
7033
7100
|
}
|
|
7034
7101
|
break;
|
|
@@ -7098,6 +7165,39 @@
|
|
|
7098
7165
|
}
|
|
7099
7166
|
}
|
|
7100
7167
|
|
|
7168
|
+
function updateApprovalStats() {
|
|
7169
|
+
var container = document.getElementById("approval-stats");
|
|
7170
|
+
if (!container) return;
|
|
7171
|
+
var selectedSession = state.sessions.find(function(s) { return s.id === state.selectedId; });
|
|
7172
|
+
var stats = selectedSession && selectedSession.approvalStats;
|
|
7173
|
+
if (!stats || stats.total === 0) {
|
|
7174
|
+
container.className = "approval-stats hidden";
|
|
7175
|
+
container.innerHTML = "";
|
|
7176
|
+
return;
|
|
7177
|
+
}
|
|
7178
|
+
container.className = "approval-stats";
|
|
7179
|
+
container.innerHTML =
|
|
7180
|
+
'<span class="approval-stats-divider"></span>' +
|
|
7181
|
+
'<span class="approval-stats-badge" id="approval-stats-badge" title="本次会话自动批准统计">' +
|
|
7182
|
+
'<svg class="approval-stats-icon" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/></svg>' +
|
|
7183
|
+
'<span class="approval-stats-total">' + stats.total + '</span>' +
|
|
7184
|
+
'</span>' +
|
|
7185
|
+
'<span class="approval-stats-popup" id="approval-stats-popup">' +
|
|
7186
|
+
'<span class="approval-stats-popup-title">自动批准统计</span>' +
|
|
7187
|
+
(stats.command > 0 ? '<span class="approval-stats-row"><span class="approval-stats-row-icon">⚡</span><span class="approval-stats-row-label">命令执行</span><span class="approval-stats-row-count">' + stats.command + '</span></span>' : '') +
|
|
7188
|
+
(stats.file > 0 ? '<span class="approval-stats-row"><span class="approval-stats-row-icon">📝</span><span class="approval-stats-row-label">文件写入</span><span class="approval-stats-row-count">' + stats.file + '</span></span>' : '') +
|
|
7189
|
+
(stats.tool > 0 ? '<span class="approval-stats-row"><span class="approval-stats-row-icon">🔧</span><span class="approval-stats-row-label">其他工具</span><span class="approval-stats-row-count">' + stats.tool + '</span></span>' : '') +
|
|
7190
|
+
'<span class="approval-stats-row approval-stats-row-total"><span class="approval-stats-row-icon">∑</span><span class="approval-stats-row-label">合计</span><span class="approval-stats-row-count">' + stats.total + '</span></span>' +
|
|
7191
|
+
'</span>';
|
|
7192
|
+
// Pulse animation on the badge
|
|
7193
|
+
var badge = container.querySelector(".approval-stats-badge");
|
|
7194
|
+
if (badge) {
|
|
7195
|
+
badge.classList.remove("approval-stats-pulse");
|
|
7196
|
+
void badge.offsetWidth;
|
|
7197
|
+
badge.classList.add("approval-stats-pulse");
|
|
7198
|
+
}
|
|
7199
|
+
}
|
|
7200
|
+
|
|
7101
7201
|
function approvePermission() {
|
|
7102
7202
|
if (!state.selectedId) return;
|
|
7103
7203
|
var approveBtn = document.getElementById("approve-permission-btn");
|
|
@@ -7856,8 +7956,12 @@
|
|
|
7856
7956
|
if (!state.terminalDomView || !state.serializeAddon) return;
|
|
7857
7957
|
|
|
7858
7958
|
try {
|
|
7959
|
+
// Serialize the entire buffer including scrollback history
|
|
7960
|
+
var buf = state.terminal.buffer.active;
|
|
7961
|
+
var totalRows = buf.length;
|
|
7859
7962
|
var html = state.serializeAddon.serializeAsHTML({
|
|
7860
|
-
includeGlobalBackground: true
|
|
7963
|
+
includeGlobalBackground: true,
|
|
7964
|
+
range: { start: 0, end: totalRows }
|
|
7861
7965
|
});
|
|
7862
7966
|
|
|
7863
7967
|
// Extract the <pre>...</pre> portion
|
|
@@ -269,8 +269,9 @@
|
|
|
269
269
|
}
|
|
270
270
|
|
|
271
271
|
.floating-sidebar-toggle.active {
|
|
272
|
-
|
|
273
|
-
|
|
272
|
+
opacity: 0;
|
|
273
|
+
pointer-events: none;
|
|
274
|
+
transition: opacity 0.15s ease;
|
|
274
275
|
}
|
|
275
276
|
|
|
276
277
|
.floating-sidebar-toggle:active {
|
|
@@ -3429,6 +3430,114 @@
|
|
|
3429
3430
|
to { opacity: 1; transform: translateX(0); }
|
|
3430
3431
|
}
|
|
3431
3432
|
|
|
3433
|
+
/* Approval stats badge */
|
|
3434
|
+
.approval-stats {
|
|
3435
|
+
display: inline-flex;
|
|
3436
|
+
align-items: center;
|
|
3437
|
+
position: relative;
|
|
3438
|
+
}
|
|
3439
|
+
.approval-stats.hidden {
|
|
3440
|
+
display: none;
|
|
3441
|
+
}
|
|
3442
|
+
.approval-stats-divider {
|
|
3443
|
+
width: 1px;
|
|
3444
|
+
height: 16px;
|
|
3445
|
+
background: var(--border);
|
|
3446
|
+
margin: 0 4px 0 2px;
|
|
3447
|
+
}
|
|
3448
|
+
.approval-stats-badge {
|
|
3449
|
+
display: inline-flex;
|
|
3450
|
+
align-items: center;
|
|
3451
|
+
gap: 3px;
|
|
3452
|
+
padding: 1px 7px 1px 5px;
|
|
3453
|
+
border-radius: 10px;
|
|
3454
|
+
background: rgba(34, 197, 94, 0.1);
|
|
3455
|
+
color: #22c55e;
|
|
3456
|
+
font-size: 0.68rem;
|
|
3457
|
+
font-weight: 600;
|
|
3458
|
+
cursor: default;
|
|
3459
|
+
transition: background 0.15s;
|
|
3460
|
+
line-height: 1.5;
|
|
3461
|
+
}
|
|
3462
|
+
.approval-stats-badge:hover {
|
|
3463
|
+
background: rgba(34, 197, 94, 0.18);
|
|
3464
|
+
}
|
|
3465
|
+
.approval-stats-icon {
|
|
3466
|
+
stroke: #22c55e;
|
|
3467
|
+
flex-shrink: 0;
|
|
3468
|
+
}
|
|
3469
|
+
.approval-stats-total {
|
|
3470
|
+
font-variant-numeric: tabular-nums;
|
|
3471
|
+
}
|
|
3472
|
+
@keyframes approval-pulse {
|
|
3473
|
+
0% { transform: scale(1); }
|
|
3474
|
+
50% { transform: scale(1.15); }
|
|
3475
|
+
100% { transform: scale(1); }
|
|
3476
|
+
}
|
|
3477
|
+
.approval-stats-pulse {
|
|
3478
|
+
animation: approval-pulse 0.3s ease-out;
|
|
3479
|
+
}
|
|
3480
|
+
/* Popup tooltip */
|
|
3481
|
+
.approval-stats-popup {
|
|
3482
|
+
display: none;
|
|
3483
|
+
position: absolute;
|
|
3484
|
+
bottom: calc(100% + 6px);
|
|
3485
|
+
left: 50%;
|
|
3486
|
+
transform: translateX(-50%);
|
|
3487
|
+
background: var(--bg-elevated, #1e1e2e);
|
|
3488
|
+
border: 1px solid var(--border);
|
|
3489
|
+
border-radius: 8px;
|
|
3490
|
+
padding: 8px 10px;
|
|
3491
|
+
min-width: 140px;
|
|
3492
|
+
box-shadow: 0 4px 16px rgba(0,0,0,0.25);
|
|
3493
|
+
z-index: 100;
|
|
3494
|
+
flex-direction: column;
|
|
3495
|
+
gap: 4px;
|
|
3496
|
+
}
|
|
3497
|
+
.approval-stats:hover .approval-stats-popup {
|
|
3498
|
+
display: flex;
|
|
3499
|
+
}
|
|
3500
|
+
.approval-stats-popup-title {
|
|
3501
|
+
font-size: 0.65rem;
|
|
3502
|
+
color: var(--text-muted);
|
|
3503
|
+
font-weight: 500;
|
|
3504
|
+
margin-bottom: 2px;
|
|
3505
|
+
white-space: nowrap;
|
|
3506
|
+
}
|
|
3507
|
+
.approval-stats-row {
|
|
3508
|
+
display: flex;
|
|
3509
|
+
align-items: center;
|
|
3510
|
+
gap: 6px;
|
|
3511
|
+
font-size: 0.7rem;
|
|
3512
|
+
color: var(--text-secondary, #ccc);
|
|
3513
|
+
white-space: nowrap;
|
|
3514
|
+
}
|
|
3515
|
+
.approval-stats-row-icon {
|
|
3516
|
+
width: 16px;
|
|
3517
|
+
text-align: center;
|
|
3518
|
+
flex-shrink: 0;
|
|
3519
|
+
font-size: 0.72rem;
|
|
3520
|
+
}
|
|
3521
|
+
.approval-stats-row-label {
|
|
3522
|
+
flex: 1;
|
|
3523
|
+
}
|
|
3524
|
+
.approval-stats-row-count {
|
|
3525
|
+
font-weight: 600;
|
|
3526
|
+
color: var(--text-primary, #eee);
|
|
3527
|
+
font-variant-numeric: tabular-nums;
|
|
3528
|
+
min-width: 20px;
|
|
3529
|
+
text-align: right;
|
|
3530
|
+
}
|
|
3531
|
+
.approval-stats-row-total {
|
|
3532
|
+
border-top: 1px solid var(--border);
|
|
3533
|
+
padding-top: 4px;
|
|
3534
|
+
margin-top: 2px;
|
|
3535
|
+
color: #22c55e;
|
|
3536
|
+
}
|
|
3537
|
+
.approval-stats-row-total .approval-stats-row-count {
|
|
3538
|
+
color: #22c55e;
|
|
3539
|
+
}
|
|
3540
|
+
|
|
3432
3541
|
.composer-interactive-toggle {
|
|
3433
3542
|
display: inline-flex;
|
|
3434
3543
|
align-items: center;
|