claude-code-kanban 4.5.0 → 4.6.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/lib/parsers.js +74 -5
- package/package.json +1 -1
- package/public/app.js +69 -12
- package/public/style.css +121 -77
- package/server.js +9 -0
package/lib/parsers.js
CHANGED
|
@@ -107,6 +107,15 @@ function parseJsonlLine(line) {
|
|
|
107
107
|
};
|
|
108
108
|
}
|
|
109
109
|
|
|
110
|
+
if (obj.type === 'queue-operation' && obj.operation === 'enqueue') {
|
|
111
|
+
return {
|
|
112
|
+
...base,
|
|
113
|
+
role: 'user',
|
|
114
|
+
queued: true,
|
|
115
|
+
content: typeof obj.content === 'string' ? obj.content : null
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
110
119
|
if (obj.type === 'progress') {
|
|
111
120
|
return { ...base, cwd: obj.cwd || null, version: obj.version || null, slug: obj.slug || null };
|
|
112
121
|
}
|
|
@@ -118,6 +127,25 @@ const TOOL_RESULT_MAX = 1500;
|
|
|
118
127
|
const USER_TEXT_MAX = 500;
|
|
119
128
|
const INTERRUPT_MARKER = '[Request interrupted by user]';
|
|
120
129
|
|
|
130
|
+
// Newer Claude Code no longer inlines pasted images as base64 blocks. It writes
|
|
131
|
+
// them to ~/.claude/image-cache/<sessionId>/<N>.<ext> and leaves only a text marker
|
|
132
|
+
// in the message — an inline "[Image #N]" placeholder and/or a standalone
|
|
133
|
+
// "[Image: source: ...\N.ext]" line. Pull out the N references (so the UI can serve
|
|
134
|
+
// the cached file) and strip the verbose source lines from the displayed text — the
|
|
135
|
+
// rendered preview replaces them; the short "[Image #N]" placeholders stay.
|
|
136
|
+
// Runs on the per-message scan hot path, so it skips the regexes entirely unless a
|
|
137
|
+
// marker is present. Returns { refs: [{ kind: 'cache', n }], text }.
|
|
138
|
+
function parseImageMarkers(text) {
|
|
139
|
+
const raw = text || '';
|
|
140
|
+
if (!raw.includes('[Image')) return { refs: [], text: raw.trim() };
|
|
141
|
+
const ns = new Set();
|
|
142
|
+
for (const m of raw.matchAll(/\[Image #(\d+)\]/g)) ns.add(Number(m[1]));
|
|
143
|
+
for (const m of raw.matchAll(/\[Image: source:[^\]]*?(\d+)\.[a-z0-9]+\]/gi)) ns.add(Number(m[1]));
|
|
144
|
+
const refs = [...ns].sort((a, b) => a - b).map((n) => ({ kind: 'cache', n }));
|
|
145
|
+
const cleaned = raw.replace(/\[Image: source:[^\]]*\]/gi, '').trim();
|
|
146
|
+
return { refs, text: cleaned };
|
|
147
|
+
}
|
|
148
|
+
|
|
121
149
|
function pushUserMessage(messages, text, timestamp, sysLabel, extras) {
|
|
122
150
|
if (sysLabel === '__skip__') return;
|
|
123
151
|
const safeText = text || '';
|
|
@@ -133,6 +161,7 @@ function pushUserMessage(messages, text, timestamp, sysLabel, extras) {
|
|
|
133
161
|
if (extras.uuid) msg.uuid = extras.uuid;
|
|
134
162
|
if (extras.images && extras.images.length) msg.images = extras.images;
|
|
135
163
|
if (extras.toolResultRefs && extras.toolResultRefs.length) msg.toolResultRefs = extras.toolResultRefs;
|
|
164
|
+
if (extras.queued) msg.queued = true;
|
|
136
165
|
}
|
|
137
166
|
messages.push(msg);
|
|
138
167
|
}
|
|
@@ -669,6 +698,7 @@ function readRecentMessages(jsonlPath, limit = 10) {
|
|
|
669
698
|
texts.push(block.text);
|
|
670
699
|
} else if (block.type === 'image' && block.source && block.source.type === 'base64') {
|
|
671
700
|
images.push({
|
|
701
|
+
kind: 'base64',
|
|
672
702
|
blockIndex: idx,
|
|
673
703
|
mediaType: block.source.media_type || 'image/png',
|
|
674
704
|
dataLen: typeof block.source.data === 'string' ? block.source.data.length : 0
|
|
@@ -709,18 +739,31 @@ function readRecentMessages(jsonlPath, limit = 10) {
|
|
|
709
739
|
}
|
|
710
740
|
});
|
|
711
741
|
const joined = texts.join('\n').trim();
|
|
712
|
-
|
|
713
|
-
|
|
742
|
+
// Prefer inline base64 blocks when present; otherwise fall back to
|
|
743
|
+
// file-cache references parsed from the text markers.
|
|
744
|
+
const { refs, text: displayText } = parseImageMarkers(joined);
|
|
745
|
+
const allImages = images.length ? images : refs;
|
|
746
|
+
const hasText = displayText && displayText !== INTERRUPT_MARKER;
|
|
747
|
+
const hasImages = allImages.length > 0;
|
|
714
748
|
if (hasText || hasImages) {
|
|
715
749
|
pushUserMessage(
|
|
716
750
|
messages,
|
|
717
|
-
|
|
751
|
+
displayText,
|
|
718
752
|
obj.timestamp,
|
|
719
|
-
getSystemMessageLabel(
|
|
720
|
-
{ uuid: obj.uuid, images, toolResultRefs: hasText ? toolResultRefs : [] }
|
|
753
|
+
getSystemMessageLabel(displayText),
|
|
754
|
+
{ uuid: obj.uuid, images: allImages, toolResultRefs: hasText ? toolResultRefs : [] }
|
|
721
755
|
);
|
|
722
756
|
}
|
|
723
757
|
}
|
|
758
|
+
} else if (obj.type === 'queue-operation' && obj.operation === 'enqueue') {
|
|
759
|
+
// Queued messages are stored as top-level `content`, not under
|
|
760
|
+
// message.content, and never re-emitted as a type:'user' line.
|
|
761
|
+
// Surface them so the Session Log reflects what the user sent.
|
|
762
|
+
const qt = typeof obj.content === 'string' ? obj.content : '';
|
|
763
|
+
if (qt) {
|
|
764
|
+
const { refs, text } = parseImageMarkers(qt);
|
|
765
|
+
pushUserMessage(messages, text, obj.timestamp, null, { queued: true, images: refs });
|
|
766
|
+
}
|
|
724
767
|
}
|
|
725
768
|
} catch (e) { /* partial line */ }
|
|
726
769
|
}
|
|
@@ -821,6 +864,31 @@ function readUserImage(jsonlPath, msgUuid, blockIndex) {
|
|
|
821
864
|
return null;
|
|
822
865
|
}
|
|
823
866
|
|
|
867
|
+
const CACHED_IMAGE_MIME = {
|
|
868
|
+
png: 'image/png', jpg: 'image/jpeg', jpeg: 'image/jpeg', gif: 'image/gif', webp: 'image/webp',
|
|
869
|
+
};
|
|
870
|
+
|
|
871
|
+
// Serve pasted image N from ~/.claude/image-cache/<sessionId>/. The cached file may
|
|
872
|
+
// be any image format, so resolve it by index rather than assuming .png. sessionId
|
|
873
|
+
// is validated to a bare id to prevent path traversal.
|
|
874
|
+
function readCachedImage(sessionId, n) {
|
|
875
|
+
const idx = Number(n);
|
|
876
|
+
if (!sessionId || !/^[A-Za-z0-9._-]+$/.test(sessionId)) return null;
|
|
877
|
+
if (!Number.isInteger(idx) || idx < 0) return null;
|
|
878
|
+
const dir = path.join(require('os').homedir(), '.claude', 'image-cache', sessionId);
|
|
879
|
+
try {
|
|
880
|
+
const match = readdirSync(dir).find((f) => {
|
|
881
|
+
const dot = f.lastIndexOf('.');
|
|
882
|
+
return dot > 0 && f.slice(0, dot) === String(idx) && CACHED_IMAGE_MIME[f.slice(dot + 1).toLowerCase()];
|
|
883
|
+
});
|
|
884
|
+
if (!match) return null;
|
|
885
|
+
const ext = match.slice(match.lastIndexOf('.') + 1).toLowerCase();
|
|
886
|
+
return { mediaType: CACHED_IMAGE_MIME[ext], buffer: readFileSync(path.join(dir, match)) };
|
|
887
|
+
} catch (_) {
|
|
888
|
+
return null;
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
|
|
824
892
|
function readMessagesPage(jsonlPath, limit = 10, beforeTimestamp = null) {
|
|
825
893
|
const fetchLimit = limit + 1;
|
|
826
894
|
const applyFilter = beforeTimestamp
|
|
@@ -1237,6 +1305,7 @@ module.exports = {
|
|
|
1237
1305
|
readMessagesPage,
|
|
1238
1306
|
readFullToolResult,
|
|
1239
1307
|
readUserImage,
|
|
1308
|
+
readCachedImage,
|
|
1240
1309
|
updateLoopInfo,
|
|
1241
1310
|
buildLoopInfoFromState,
|
|
1242
1311
|
buildAgentProgressMap,
|
package/package.json
CHANGED
package/public/app.js
CHANGED
|
@@ -27,6 +27,8 @@ let agentDurationInterval = null;
|
|
|
27
27
|
let agentPollInterval = null;
|
|
28
28
|
let selectedTaskId = null;
|
|
29
29
|
let selectedSessionId = null;
|
|
30
|
+
// Task stays selected (keyboard nav) but its white highlight is dimmed once the detail panel closes.
|
|
31
|
+
let taskHighlightDimmed = false;
|
|
30
32
|
let focusZone = 'board'; // 'board' | 'sidebar'
|
|
31
33
|
let appConfig = { marketplaceUrl: null, costUrl: null, memoryUrl: null };
|
|
32
34
|
let selectedSessionIdx = -1;
|
|
@@ -1029,7 +1031,7 @@ function renderToolItem(m, i, compact) {
|
|
|
1029
1031
|
? `onclick="showAgentModal('${escapeHtml(m.agentId)}')" ${combinedStyle}`
|
|
1030
1032
|
: `onclick="msgDetailFollowLatest=false;showMsgDetail(${i})" ${combinedStyle}`;
|
|
1031
1033
|
const pinBtn = renderMsgPinBtn(m, i);
|
|
1032
|
-
return `<div class="msg-item msg-tool${compactClass}" ${itemClickAttr}>
|
|
1034
|
+
return `<div class="msg-item msg-tool${compactClass}" data-msg-idx="${i}" ${itemClickAttr}>
|
|
1033
1035
|
${getToolIcon(m.tool)}
|
|
1034
1036
|
<div class="msg-body"><div class="msg-text">${escapeHtml(m.tool)}${toolDetail}${agentLink}</div><div class="msg-time">${formatDate(m.timestamp)}</div></div>${agentLogBtn}${pinBtn}
|
|
1035
1037
|
</div>`;
|
|
@@ -1071,7 +1073,7 @@ function renderMessageList(messages) {
|
|
|
1071
1073
|
continue;
|
|
1072
1074
|
}
|
|
1073
1075
|
|
|
1074
|
-
const clickable = `onclick="msgDetailFollowLatest=false;showMsgDetail(${i})" style="cursor:pointer"`;
|
|
1076
|
+
const clickable = `data-msg-idx="${i}" onclick="msgDetailFollowLatest=false;showMsgDetail(${i})" style="cursor:pointer"`;
|
|
1075
1077
|
const pinBtn = renderMsgPinBtn(m, i);
|
|
1076
1078
|
if (m.type === 'user') {
|
|
1077
1079
|
if (m.systemLabel) {
|
|
@@ -1097,9 +1099,10 @@ function renderMessageList(messages) {
|
|
|
1097
1099
|
if (displayText) textHtml = isCmd ? `<code>${escapeHtml(displayText)}</code>` : displayText;
|
|
1098
1100
|
else if (chips.length) textHtml = '<em class="msg-text-muted">(attachment)</em>';
|
|
1099
1101
|
else textHtml = '';
|
|
1100
|
-
|
|
1102
|
+
const queuedTag = m.queued ? '<span class="msg-queued-tag">queued</span>' : '';
|
|
1103
|
+
parts.push(`<div class="msg-item msg-user${isCmd ? ' msg-cmd' : ''}${m.queued ? ' msg-queued' : ''}" ${clickable}>
|
|
1101
1104
|
${MSG_ICON_USER}
|
|
1102
|
-
<div class="msg-body"><div class="msg-text">${textHtml}${cmdArgsHtml}</div>${chipsHtml}<div class="msg-time">${formatDate(m.timestamp)}</div></div>${pinBtn}
|
|
1105
|
+
<div class="msg-body"><div class="msg-text">${textHtml}${cmdArgsHtml}</div>${chipsHtml}<div class="msg-time">${formatDate(m.timestamp)}${queuedTag}</div></div>${pinBtn}
|
|
1103
1106
|
</div>`);
|
|
1104
1107
|
}
|
|
1105
1108
|
} else if (m.type === 'assistant') {
|
|
@@ -1255,6 +1258,7 @@ function renderMessages(messages) {
|
|
|
1255
1258
|
? `<div class="msg-limit-banner">Showing last ${MSG_MAX_LOADED} messages</div>`
|
|
1256
1259
|
: '';
|
|
1257
1260
|
container.innerHTML = limitBanner + msgsHtml + renderWaitingEntry();
|
|
1261
|
+
highlightSelectedMsg();
|
|
1258
1262
|
if (!msgUserScrolledUp) container.scrollTop = container.scrollHeight;
|
|
1259
1263
|
// Auto-load more if content doesn't overflow yet
|
|
1260
1264
|
if (
|
|
@@ -1269,6 +1273,8 @@ function renderMessages(messages) {
|
|
|
1269
1273
|
|
|
1270
1274
|
let currentMsgDetailIdx = null;
|
|
1271
1275
|
let msgDetailFollowLatest = false;
|
|
1276
|
+
// Message stays tracked but its highlight is dimmed once the detail modal closes.
|
|
1277
|
+
let msgHighlightDimmed = false;
|
|
1272
1278
|
const MSG_DETAIL_WAITING_IDX = -2;
|
|
1273
1279
|
let currentPins = [];
|
|
1274
1280
|
let pinnedCollapsed = false;
|
|
@@ -1287,6 +1293,8 @@ const MSG_ICON_TEAMMATE =
|
|
|
1287
1293
|
'<svg class="msg-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M23 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/></svg>';
|
|
1288
1294
|
const MSG_ICON_IDLE =
|
|
1289
1295
|
'<svg class="msg-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><path d="M12 6v6"/></svg>';
|
|
1296
|
+
const ICON_AGENT =
|
|
1297
|
+
'<svg class="msg-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="4" y="4" width="16" height="16" rx="2"/><rect x="9" y="9" width="6" height="6"/><path d="M9 2v2M15 2v2M9 20v2M15 20v2M20 9h2M20 14h2M2 9h2M2 14h2"/></svg>';
|
|
1290
1298
|
const ICON_TASK =
|
|
1291
1299
|
'<svg class="msg-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M9 11l3 3L22 4"/><path d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11"/></svg>';
|
|
1292
1300
|
const ICON_WEB =
|
|
@@ -1309,7 +1317,7 @@ const TOOL_ICONS = {
|
|
|
1309
1317
|
Edit: '<svg class="msg-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/><path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"/></svg>',
|
|
1310
1318
|
Glob: '<svg class="msg-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"/><circle cx="14" cy="14" r="3"/><line x1="16.5" y1="16.5" x2="19" y2="19"/></svg>',
|
|
1311
1319
|
Grep: '<svg class="msg-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>',
|
|
1312
|
-
Agent:
|
|
1320
|
+
Agent: ICON_AGENT,
|
|
1313
1321
|
SendMessage:
|
|
1314
1322
|
'<svg class="msg-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="22" y1="2" x2="11" y2="13"/><polygon points="22 2 15 22 11 13 2 9 22 2"/></svg>',
|
|
1315
1323
|
TaskCreate: ICON_TASK,
|
|
@@ -1646,10 +1654,20 @@ const linkSvg = (size) =>
|
|
|
1646
1654
|
//#region MODALS
|
|
1647
1655
|
function renderUserAttachments(m) {
|
|
1648
1656
|
const parts = [];
|
|
1649
|
-
if (m.images?.length &&
|
|
1657
|
+
if (m.images?.length && currentSessionId) {
|
|
1658
|
+
const sid = encodeURIComponent(currentSessionId);
|
|
1650
1659
|
const imgs = m.images
|
|
1651
1660
|
.map((img) => {
|
|
1652
|
-
|
|
1661
|
+
// Cache-kind images live on disk (image-cache/<sessionId>/<n>.png); base64
|
|
1662
|
+
// images are read from the JSONL block and need the message uuid.
|
|
1663
|
+
let url;
|
|
1664
|
+
if (img.kind === 'cache') {
|
|
1665
|
+
url = `/api/sessions/${sid}/cached-image/${img.n}`;
|
|
1666
|
+
} else if (m.uuid) {
|
|
1667
|
+
url = `/api/sessions/${sid}/user-image/${encodeURIComponent(m.uuid)}/${img.blockIndex}`;
|
|
1668
|
+
} else {
|
|
1669
|
+
return '';
|
|
1670
|
+
}
|
|
1653
1671
|
return `<img src="${url}" loading="lazy" alt="user image" class="user-attach-image" />`;
|
|
1654
1672
|
})
|
|
1655
1673
|
.join('');
|
|
@@ -1678,10 +1696,22 @@ function renderUserAttachments(m) {
|
|
|
1678
1696
|
return parts.join('');
|
|
1679
1697
|
}
|
|
1680
1698
|
|
|
1699
|
+
// Highlight the message whose detail is open, mirroring task-card selection.
|
|
1700
|
+
function highlightSelectedMsg() {
|
|
1701
|
+
const container = document.getElementById('message-panel-content');
|
|
1702
|
+
if (!container) return;
|
|
1703
|
+
for (const el of container.querySelectorAll('.msg-item.selected')) el.classList.remove('selected');
|
|
1704
|
+
if (msgHighlightDimmed || currentMsgDetailIdx == null || currentMsgDetailIdx < 0) return;
|
|
1705
|
+
const el = container.querySelector(`.msg-item[data-msg-idx="${currentMsgDetailIdx}"]`);
|
|
1706
|
+
if (el) el.classList.add('selected');
|
|
1707
|
+
}
|
|
1708
|
+
|
|
1681
1709
|
function showMsgDetail(idx) {
|
|
1682
1710
|
currentMsgDetailIdx = idx;
|
|
1711
|
+
msgHighlightDimmed = false;
|
|
1683
1712
|
const m = currentMessages[idx];
|
|
1684
1713
|
if (!m) return;
|
|
1714
|
+
highlightSelectedMsg();
|
|
1685
1715
|
const body = document.getElementById('msg-detail-body');
|
|
1686
1716
|
if (m.type === 'tool_use') {
|
|
1687
1717
|
document.getElementById('msg-detail-title').textContent = m.tool;
|
|
@@ -1785,6 +1815,12 @@ function showMsgDetail(idx) {
|
|
|
1785
1815
|
function closeMsgDetailModal() {
|
|
1786
1816
|
resetModalFullscreen('msg-detail-modal');
|
|
1787
1817
|
msgDetailFollowLatest = false;
|
|
1818
|
+
// Drop the message highlight on close, mirroring task-card behavior.
|
|
1819
|
+
msgHighlightDimmed = true;
|
|
1820
|
+
const container = document.getElementById('message-panel-content');
|
|
1821
|
+
if (container) {
|
|
1822
|
+
for (const el of container.querySelectorAll('.msg-item.selected')) el.classList.remove('selected');
|
|
1823
|
+
}
|
|
1788
1824
|
}
|
|
1789
1825
|
|
|
1790
1826
|
function _setModalWidth(modal, slot, on, maxWidth, width) {
|
|
@@ -2365,7 +2401,8 @@ function renderAgentFooter() {
|
|
|
2365
2401
|
: '';
|
|
2366
2402
|
const agentColor = resolveNamedColor(a.color);
|
|
2367
2403
|
const colorStyle = agentColor ? ` style="border-left:3px solid ${agentColor.color}"` : '';
|
|
2368
|
-
|
|
2404
|
+
const selectedClass = a.agentId === currentAgentModalId ? ' selected' : '';
|
|
2405
|
+
return `<div class="agent-card${selectedClass}" data-agent-id="${escapeHtml(a.agentId)}"${colorStyle} onclick="showAgentModal('${a.agentId}')">
|
|
2369
2406
|
<div class="agent-type-row">${typeNs ? `<span class="agent-type-ns">${escapeHtml(typeNs)}</span>` : ''}<span class="agent-type-name">${escapeHtml(typeName)}</span>${nameBadgeHtml}</div>
|
|
2370
2407
|
<div class="agent-status-row"><span class="agent-dot ${a.status}"></span><span class="agent-status">${statusText}</span></div>
|
|
2371
2408
|
${msgHtml}
|
|
@@ -2457,6 +2494,7 @@ function showAgentModal(agentId) {
|
|
|
2457
2494
|
const agent = findAgentById(agentId);
|
|
2458
2495
|
if (!agent) return;
|
|
2459
2496
|
currentAgentModalId = agentId;
|
|
2497
|
+
highlightSelectedAgent();
|
|
2460
2498
|
const modal = document.getElementById('agent-modal');
|
|
2461
2499
|
const title = document.getElementById('agent-modal-title');
|
|
2462
2500
|
const body = document.getElementById('agent-modal-body');
|
|
@@ -2526,6 +2564,16 @@ function showAgentModal(agentId) {
|
|
|
2526
2564
|
function closeAgentModal() {
|
|
2527
2565
|
resetModalFullscreen('agent-modal');
|
|
2528
2566
|
currentAgentModalId = null;
|
|
2567
|
+
highlightSelectedAgent();
|
|
2568
|
+
}
|
|
2569
|
+
|
|
2570
|
+
function highlightSelectedAgent() {
|
|
2571
|
+
const content = document.getElementById('agent-footer-content');
|
|
2572
|
+
if (!content) return;
|
|
2573
|
+
for (const el of content.querySelectorAll('.agent-card.selected')) el.classList.remove('selected');
|
|
2574
|
+
if (currentAgentModalId == null) return;
|
|
2575
|
+
const el = content.querySelector(`.agent-card[data-agent-id="${currentAgentModalId}"]`);
|
|
2576
|
+
if (el) el.classList.add('selected');
|
|
2529
2577
|
}
|
|
2530
2578
|
|
|
2531
2579
|
//#endregion
|
|
@@ -2760,7 +2808,7 @@ function renderSessions() {
|
|
|
2760
2808
|
<span class="session-indicators">
|
|
2761
2809
|
${isTeam ? `<span class="team-badge" title="${memberCount} team members"><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M23 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/></svg>${memberCount}</span>` : ''}
|
|
2762
2810
|
${session.sharedTaskList ? `<span class="shared-tasklist-badge" title="Shared task list: ${escapeHtml(session.sharedTaskList)}">${linkSvg(12)}</span>` : ''}
|
|
2763
|
-
${isTeam || session.project || showCtx ? `<span class="team-info-btn" onclick="event.stopPropagation(); showSessionInfoModal('${session.id}')" title="View session info"
|
|
2811
|
+
${isTeam || session.project || showCtx ? `<span class="team-info-btn" onclick="event.stopPropagation(); showSessionInfoModal('${session.id}')" title="View session info"><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><line x1="12" y1="16" x2="12" y2="12"/><line x1="12" y1="8" x2="12.01" y2="8"/></svg></span>` : ''}
|
|
2764
2812
|
${session.hasPlan && !session.planSourceSessionId ? `<span class="plan-indicator" onclick="event.stopPropagation(); openPlanForSession('${session.id}')" title="View plan"><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" 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"/><line x1="16" y1="13" x2="8" y2="13"/><line x1="16" y1="17" x2="8" y2="17"/></svg></span>` : ''}
|
|
2765
2813
|
${renderLoopBadge(session)}
|
|
2766
2814
|
${linkedDocsCount > 0 ? `<span class="linked-docs-badge" onclick="event.stopPropagation(); showSessionInfoModal('${session.id}')" title="${linkedDocsCount} linked document${linkedDocsCount > 1 ? 's' : ''}">${linkSvg(10)}${linkedDocsCount}</span>` : ''}
|
|
@@ -3080,7 +3128,7 @@ function renderKanban() {
|
|
|
3080
3128
|
document.querySelector(`.task-card[data-task-id="${selectedTaskId}"][data-session-id="${selectedSessionId}"]`) ||
|
|
3081
3129
|
document.querySelector(`.task-card[data-task-id="${selectedTaskId}"]`);
|
|
3082
3130
|
if (card) {
|
|
3083
|
-
if (focusZone === 'board') card.classList.add('selected');
|
|
3131
|
+
if (focusZone === 'board' && !taskHighlightDimmed) card.classList.add('selected');
|
|
3084
3132
|
} else {
|
|
3085
3133
|
selectedTaskId = null;
|
|
3086
3134
|
selectedSessionId = null;
|
|
@@ -3168,6 +3216,7 @@ function selectTask(taskId, sessionId) {
|
|
|
3168
3216
|
if (prev) prev.classList.remove('selected');
|
|
3169
3217
|
selectedTaskId = taskId;
|
|
3170
3218
|
selectedSessionId = sessionId;
|
|
3219
|
+
taskHighlightDimmed = false;
|
|
3171
3220
|
if (!taskId) return;
|
|
3172
3221
|
const card =
|
|
3173
3222
|
document.querySelector(`.task-card[data-task-id="${taskId}"][data-session-id="${sessionId}"]`) ||
|
|
@@ -3408,6 +3457,8 @@ function setFocusZone(zone) {
|
|
|
3408
3457
|
}
|
|
3409
3458
|
}
|
|
3410
3459
|
} else {
|
|
3460
|
+
// Focusing the board is an explicit nav gesture — restore the highlight.
|
|
3461
|
+
taskHighlightDimmed = false;
|
|
3411
3462
|
// Session changed while in sidebar — reset stale selection
|
|
3412
3463
|
if (selectedSessionId && selectedSessionId !== currentSessionId) {
|
|
3413
3464
|
selectedTaskId = null;
|
|
@@ -3710,6 +3761,10 @@ async function addNote(event, taskId, sessionId) {
|
|
|
3710
3761
|
function closeDetailPanel() {
|
|
3711
3762
|
detailPanel.classList.remove('visible');
|
|
3712
3763
|
document.getElementById('delete-task-btn').style.display = 'none';
|
|
3764
|
+
// Keep the task selected for keyboard nav, but drop its white highlight.
|
|
3765
|
+
taskHighlightDimmed = true;
|
|
3766
|
+
const sel = document.querySelector('.task-card.selected');
|
|
3767
|
+
if (sel) sel.classList.remove('selected');
|
|
3713
3768
|
}
|
|
3714
3769
|
|
|
3715
3770
|
let deleteTaskId = null;
|
|
@@ -5284,6 +5339,8 @@ function isWaitingFresh() {
|
|
|
5284
5339
|
function showWaitingDetail() {
|
|
5285
5340
|
if (!isWaitingFresh()) return;
|
|
5286
5341
|
currentMsgDetailIdx = MSG_DETAIL_WAITING_IDX;
|
|
5342
|
+
msgHighlightDimmed = false;
|
|
5343
|
+
highlightSelectedMsg();
|
|
5287
5344
|
const tool = currentWaiting.toolName || 'unknown';
|
|
5288
5345
|
const label = getWaitingLabel(currentWaiting.kind, tool);
|
|
5289
5346
|
const body = document.getElementById('msg-detail-body');
|
|
@@ -5923,9 +5980,9 @@ function showInfoModal(session, teamConfig, tasks, planContent, parentInfo) {
|
|
|
5923
5980
|
infoRows.push(['Team Config', teamConfig.configPath, { openPath: configDir, openFile: teamConfig.configPath }]);
|
|
5924
5981
|
}
|
|
5925
5982
|
const clickableStyle =
|
|
5926
|
-
|
|
5983
|
+
'font-family: var(--mono); font-size: 12px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; cursor: pointer; color: var(--accent-text); text-decoration: underline; text-decoration-style: dotted; text-underline-offset: 3px;';
|
|
5927
5984
|
const plainStyle =
|
|
5928
|
-
|
|
5985
|
+
'font-family: var(--mono); font-size: 12px; color: var(--text-primary); user-select: all; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;';
|
|
5929
5986
|
html += `<div class="team-modal-meta info-grid">`;
|
|
5930
5987
|
infoRows.forEach(([label, value, opts]) => {
|
|
5931
5988
|
const copyVal = escapeHtml(value).replace(/"/g, '"');
|
package/public/style.css
CHANGED
|
@@ -18,16 +18,22 @@
|
|
|
18
18
|
--accent-text: #f0a070;
|
|
19
19
|
--accent-dim: rgba(232, 111, 51, 0.22);
|
|
20
20
|
--accent-glow: rgba(232, 111, 51, 0.55);
|
|
21
|
+
--gold: #d9b667;
|
|
21
22
|
--success: #3ecf8e;
|
|
22
23
|
--success-dim: rgba(62, 207, 142, 0.18);
|
|
23
24
|
--warning: #f0b429;
|
|
24
25
|
--warning-dim: rgba(240, 180, 41, 0.18);
|
|
26
|
+
--danger: #ef5350;
|
|
27
|
+
--danger-dim: rgba(239, 83, 80, 0.18);
|
|
25
28
|
--team: #60a5fa;
|
|
26
29
|
--team-dim: rgba(96, 165, 250, 0.18);
|
|
27
30
|
--plan: #86a886;
|
|
28
31
|
--plan-dim: rgba(134, 168, 134, 0.18);
|
|
29
32
|
--mono: 'IBM Plex Mono', monospace;
|
|
30
33
|
--serif: 'Playfair Display', serif;
|
|
34
|
+
/* aliases: many rules reference --font-mono/--font-serif; keep them on-brand */
|
|
35
|
+
--font-mono: var(--mono);
|
|
36
|
+
--font-serif: var(--serif);
|
|
31
37
|
}
|
|
32
38
|
|
|
33
39
|
* {
|
|
@@ -85,7 +91,7 @@ body::before {
|
|
|
85
91
|
.sidebar {
|
|
86
92
|
width: var(--sidebar-width, 300px);
|
|
87
93
|
background: var(--bg-surface);
|
|
88
|
-
border-right: 1px solid var(--border);
|
|
94
|
+
border-right: 1px solid color-mix(in srgb, var(--border) 35%, transparent);
|
|
89
95
|
box-shadow: 1px 0 12px rgba(0, 0, 0, 0.04);
|
|
90
96
|
display: flex;
|
|
91
97
|
flex-direction: column;
|
|
@@ -510,7 +516,7 @@ body::before {
|
|
|
510
516
|
color: var(--text-secondary);
|
|
511
517
|
margin-top: 2px;
|
|
512
518
|
display: block;
|
|
513
|
-
font-family:
|
|
519
|
+
font-family: var(--mono);
|
|
514
520
|
white-space: nowrap;
|
|
515
521
|
overflow: hidden;
|
|
516
522
|
text-overflow: ellipsis;
|
|
@@ -656,10 +662,10 @@ body::before {
|
|
|
656
662
|
gap: 8px;
|
|
657
663
|
}
|
|
658
664
|
.detail-context-stats .stat-label {
|
|
659
|
-
color: var(--text-
|
|
665
|
+
color: var(--text-secondary);
|
|
660
666
|
}
|
|
661
667
|
.detail-context-stats .stat-value {
|
|
662
|
-
color: var(--text-
|
|
668
|
+
color: var(--text-primary);
|
|
663
669
|
}
|
|
664
670
|
.detail-context-stats .stat-divider {
|
|
665
671
|
grid-column: 1 / -1;
|
|
@@ -811,7 +817,7 @@ body::before {
|
|
|
811
817
|
|
|
812
818
|
/* #region HEADER */
|
|
813
819
|
.view-header {
|
|
814
|
-
padding:
|
|
820
|
+
padding: 12px 24px;
|
|
815
821
|
border-bottom: 1px solid var(--border);
|
|
816
822
|
background: var(--bg-surface);
|
|
817
823
|
display: flex;
|
|
@@ -821,9 +827,11 @@ body::before {
|
|
|
821
827
|
|
|
822
828
|
.view-title {
|
|
823
829
|
font-family: var(--serif);
|
|
824
|
-
font-size:
|
|
830
|
+
font-size: 20px;
|
|
825
831
|
font-weight: 400;
|
|
826
832
|
letter-spacing: -0.03em;
|
|
833
|
+
line-height: 1.15;
|
|
834
|
+
text-wrap: balance;
|
|
827
835
|
display: flex;
|
|
828
836
|
align-items: center;
|
|
829
837
|
gap: 12px;
|
|
@@ -1018,12 +1026,12 @@ body::before {
|
|
|
1018
1026
|
|
|
1019
1027
|
/* #region TASK_CARD */
|
|
1020
1028
|
.task-card {
|
|
1021
|
-
padding:
|
|
1022
|
-
padding-left:
|
|
1029
|
+
padding: 10px 12px;
|
|
1030
|
+
padding-left: 14px;
|
|
1023
1031
|
background: var(--bg-surface);
|
|
1024
1032
|
border: 1px solid var(--border);
|
|
1025
1033
|
border-left: 2px solid var(--text-muted);
|
|
1026
|
-
border-radius:
|
|
1034
|
+
border-radius: 6px;
|
|
1027
1035
|
cursor: pointer;
|
|
1028
1036
|
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
|
|
1029
1037
|
}
|
|
@@ -1067,18 +1075,18 @@ body::before {
|
|
|
1067
1075
|
.task-card.selected,
|
|
1068
1076
|
.task-card.selected:hover {
|
|
1069
1077
|
background: var(--bg-elevated);
|
|
1070
|
-
border-color: var(--
|
|
1071
|
-
border-left: 2px solid var(--
|
|
1078
|
+
border-color: var(--accent);
|
|
1079
|
+
border-left: 2px solid var(--accent);
|
|
1072
1080
|
box-shadow:
|
|
1073
|
-
0 0 0 1px var(--
|
|
1074
|
-
0 0 12px var(--
|
|
1081
|
+
0 0 0 1px var(--accent-dim),
|
|
1082
|
+
0 0 12px var(--accent-dim);
|
|
1075
1083
|
opacity: 1;
|
|
1076
1084
|
}
|
|
1077
1085
|
|
|
1078
1086
|
.task-id {
|
|
1079
1087
|
font-size: 11px;
|
|
1080
1088
|
color: var(--text-muted);
|
|
1081
|
-
margin-bottom:
|
|
1089
|
+
margin-bottom: 4px;
|
|
1082
1090
|
display: flex;
|
|
1083
1091
|
align-items: center;
|
|
1084
1092
|
gap: 8px;
|
|
@@ -1099,9 +1107,9 @@ body::before {
|
|
|
1099
1107
|
}
|
|
1100
1108
|
|
|
1101
1109
|
.task-title {
|
|
1102
|
-
font-size:
|
|
1110
|
+
font-size: 13px;
|
|
1103
1111
|
color: var(--text-primary);
|
|
1104
|
-
line-height: 1.
|
|
1112
|
+
line-height: 1.35;
|
|
1105
1113
|
}
|
|
1106
1114
|
|
|
1107
1115
|
.task-card.completed .task-title {
|
|
@@ -1112,15 +1120,15 @@ body::before {
|
|
|
1112
1120
|
.task-session {
|
|
1113
1121
|
font-size: 12px;
|
|
1114
1122
|
color: var(--accent);
|
|
1115
|
-
margin-top:
|
|
1123
|
+
margin-top: 4px;
|
|
1116
1124
|
}
|
|
1117
1125
|
|
|
1118
1126
|
.task-active {
|
|
1119
1127
|
display: flex;
|
|
1120
1128
|
align-items: center;
|
|
1121
1129
|
gap: 6px;
|
|
1122
|
-
margin-top:
|
|
1123
|
-
padding-top:
|
|
1130
|
+
margin-top: 8px;
|
|
1131
|
+
padding-top: 8px;
|
|
1124
1132
|
border-top: 1px solid var(--border);
|
|
1125
1133
|
font-size: 11px;
|
|
1126
1134
|
font-weight: 500;
|
|
@@ -1147,7 +1155,7 @@ body::before {
|
|
|
1147
1155
|
font-size: 12px;
|
|
1148
1156
|
font-weight: 450;
|
|
1149
1157
|
color: var(--text-tertiary);
|
|
1150
|
-
margin-top:
|
|
1158
|
+
margin-top: 6px;
|
|
1151
1159
|
display: -webkit-box;
|
|
1152
1160
|
-webkit-line-clamp: 2;
|
|
1153
1161
|
-webkit-box-orient: vertical;
|
|
@@ -1164,7 +1172,7 @@ body::before {
|
|
|
1164
1172
|
width: var(--detail-panel-width, 540px);
|
|
1165
1173
|
height: 100vh;
|
|
1166
1174
|
background: var(--bg-surface);
|
|
1167
|
-
border-left: 1px solid var(--border);
|
|
1175
|
+
border-left: 1px solid color-mix(in srgb, var(--border) 35%, transparent);
|
|
1168
1176
|
box-shadow: -8px 0 24px rgba(0, 0, 0, 0.15);
|
|
1169
1177
|
display: none;
|
|
1170
1178
|
flex-direction: column;
|
|
@@ -1441,13 +1449,10 @@ body::before {
|
|
|
1441
1449
|
font-size: 12px;
|
|
1442
1450
|
}
|
|
1443
1451
|
|
|
1444
|
-
.detail-desc pre code.hljs
|
|
1445
|
-
padding: 12px;
|
|
1446
|
-
border-radius: 6px;
|
|
1447
|
-
}
|
|
1448
|
-
|
|
1452
|
+
.detail-desc pre code.hljs,
|
|
1449
1453
|
.detail-desc pre code {
|
|
1450
|
-
background: var(--
|
|
1454
|
+
background: color-mix(in srgb, var(--text-muted) 8%, transparent);
|
|
1455
|
+
border: 1px solid color-mix(in srgb, var(--border) 55%, transparent);
|
|
1451
1456
|
padding: 12px;
|
|
1452
1457
|
border-radius: 6px;
|
|
1453
1458
|
display: block;
|
|
@@ -1455,9 +1460,10 @@ body::before {
|
|
|
1455
1460
|
}
|
|
1456
1461
|
|
|
1457
1462
|
.detail-desc code {
|
|
1458
|
-
background: var(--
|
|
1459
|
-
|
|
1460
|
-
|
|
1463
|
+
background: color-mix(in srgb, var(--text-muted) 12%, transparent);
|
|
1464
|
+
border: 1px solid color-mix(in srgb, var(--border) 45%, transparent);
|
|
1465
|
+
padding: 1px 5px;
|
|
1466
|
+
border-radius: 4px;
|
|
1461
1467
|
font-size: 0.9em;
|
|
1462
1468
|
}
|
|
1463
1469
|
|
|
@@ -1588,18 +1594,19 @@ body::before {
|
|
|
1588
1594
|
align-items: center;
|
|
1589
1595
|
justify-content: center;
|
|
1590
1596
|
font-size: 11px;
|
|
1591
|
-
background:
|
|
1597
|
+
background: transparent;
|
|
1592
1598
|
border: 1px solid transparent;
|
|
1593
1599
|
border-radius: 4px;
|
|
1594
|
-
color: var(--
|
|
1600
|
+
color: var(--text-muted);
|
|
1595
1601
|
cursor: pointer;
|
|
1596
1602
|
flex-shrink: 0;
|
|
1597
1603
|
transition: all 0.15s ease;
|
|
1598
1604
|
}
|
|
1599
1605
|
|
|
1600
1606
|
.team-info-btn:hover {
|
|
1601
|
-
background: var(--
|
|
1602
|
-
border-color: var(--
|
|
1607
|
+
background: var(--bg-hover);
|
|
1608
|
+
border-color: var(--border);
|
|
1609
|
+
color: var(--text-secondary);
|
|
1603
1610
|
}
|
|
1604
1611
|
|
|
1605
1612
|
.plan-indicator {
|
|
@@ -1737,10 +1744,10 @@ body::before {
|
|
|
1737
1744
|
}
|
|
1738
1745
|
|
|
1739
1746
|
.loop-field-val code {
|
|
1740
|
-
background: var(--bg);
|
|
1747
|
+
background: var(--bg-deep);
|
|
1741
1748
|
padding: 2px 6px;
|
|
1742
1749
|
border-radius: 4px;
|
|
1743
|
-
font-family:
|
|
1750
|
+
font-family: var(--mono);
|
|
1744
1751
|
font-size: 12px;
|
|
1745
1752
|
}
|
|
1746
1753
|
|
|
@@ -1830,8 +1837,8 @@ body::before {
|
|
|
1830
1837
|
font-size: 11px;
|
|
1831
1838
|
font-weight: 500;
|
|
1832
1839
|
border-radius: 999px;
|
|
1833
|
-
border: 1px solid var(--border
|
|
1834
|
-
background: var(--bg-
|
|
1840
|
+
border: 1px solid var(--border);
|
|
1841
|
+
background: var(--bg-surface);
|
|
1835
1842
|
color: var(--text-secondary);
|
|
1836
1843
|
white-space: nowrap;
|
|
1837
1844
|
line-height: 1.4;
|
|
@@ -2002,8 +2009,8 @@ body::before {
|
|
|
2002
2009
|
right: 0;
|
|
2003
2010
|
width: var(--message-panel-width, 540px);
|
|
2004
2011
|
height: 100vh;
|
|
2005
|
-
background: var(--bg-
|
|
2006
|
-
border-left: 1px solid var(--border);
|
|
2012
|
+
background: var(--bg-deep);
|
|
2013
|
+
border-left: 1px solid color-mix(in srgb, var(--border) 35%, transparent);
|
|
2007
2014
|
box-shadow: -8px 0 24px rgba(0, 0, 0, 0.15);
|
|
2008
2015
|
display: none;
|
|
2009
2016
|
flex-direction: column;
|
|
@@ -2047,22 +2054,39 @@ body::before {
|
|
|
2047
2054
|
padding: 8px 10px;
|
|
2048
2055
|
border-radius: 6px;
|
|
2049
2056
|
font-size: 12px;
|
|
2050
|
-
line-height: 1.
|
|
2051
|
-
background: var(--bg-
|
|
2052
|
-
border: 1px solid var(--border);
|
|
2057
|
+
line-height: 1.4;
|
|
2058
|
+
background: var(--bg-surface);
|
|
2059
|
+
border: 1px solid color-mix(in srgb, var(--border) 65%, transparent);
|
|
2053
2060
|
}
|
|
2054
2061
|
.msg-item.msg-user {
|
|
2055
|
-
border-left: 3px solid var(--
|
|
2062
|
+
border-left: 3px solid var(--gold);
|
|
2063
|
+
}
|
|
2064
|
+
.msg-queued-tag {
|
|
2065
|
+
margin-left: 8px;
|
|
2066
|
+
padding: 0 5px;
|
|
2067
|
+
border-radius: 3px;
|
|
2068
|
+
font-size: 9px;
|
|
2069
|
+
letter-spacing: 0.06em;
|
|
2070
|
+
text-transform: uppercase;
|
|
2071
|
+
color: var(--gold);
|
|
2072
|
+
background: color-mix(in srgb, var(--gold) 14%, transparent);
|
|
2056
2073
|
}
|
|
2057
2074
|
.msg-item.msg-assistant {
|
|
2058
2075
|
border-left: 3px solid var(--accent);
|
|
2059
2076
|
}
|
|
2060
2077
|
.msg-item.msg-tool {
|
|
2061
|
-
border-left:
|
|
2078
|
+
border-left: 2px solid color-mix(in srgb, var(--text-muted) 45%, transparent);
|
|
2062
2079
|
font-size: 11px;
|
|
2063
2080
|
padding: 5px 10px;
|
|
2064
2081
|
color: var(--text-secondary);
|
|
2065
2082
|
}
|
|
2083
|
+
.msg-item.selected,
|
|
2084
|
+
.msg-item.selected:hover {
|
|
2085
|
+
background: var(--bg-elevated);
|
|
2086
|
+
box-shadow:
|
|
2087
|
+
0 0 0 1px var(--accent),
|
|
2088
|
+
0 0 12px var(--accent-dim);
|
|
2089
|
+
}
|
|
2066
2090
|
.msg-icon {
|
|
2067
2091
|
flex-shrink: 0;
|
|
2068
2092
|
width: 16px;
|
|
@@ -2093,7 +2117,7 @@ body::before {
|
|
|
2093
2117
|
display: inline-flex;
|
|
2094
2118
|
align-items: center;
|
|
2095
2119
|
justify-content: center;
|
|
2096
|
-
background: var(--hover);
|
|
2120
|
+
background: var(--bg-hover);
|
|
2097
2121
|
color: var(--text-secondary);
|
|
2098
2122
|
font-size: 10px;
|
|
2099
2123
|
font-weight: 600;
|
|
@@ -2213,7 +2237,7 @@ body::before {
|
|
|
2213
2237
|
}
|
|
2214
2238
|
.pinned-item-unpin:hover {
|
|
2215
2239
|
opacity: 1;
|
|
2216
|
-
color: var(--danger
|
|
2240
|
+
color: var(--danger);
|
|
2217
2241
|
}
|
|
2218
2242
|
.msg-pin-btn.pinned svg,
|
|
2219
2243
|
#msg-detail-pin-btn.active svg,
|
|
@@ -2313,7 +2337,7 @@ body::before {
|
|
|
2313
2337
|
}
|
|
2314
2338
|
.agent-tab-copy {
|
|
2315
2339
|
margin-left: auto;
|
|
2316
|
-
background: var(--
|
|
2340
|
+
background: var(--bg-hover);
|
|
2317
2341
|
border: 1px solid var(--border);
|
|
2318
2342
|
border-radius: 6px;
|
|
2319
2343
|
padding: 4px 6px;
|
|
@@ -2431,8 +2455,9 @@ body::before {
|
|
|
2431
2455
|
font-size: 0.85rem;
|
|
2432
2456
|
}
|
|
2433
2457
|
.msg-detail-pre-tinted {
|
|
2434
|
-
background:
|
|
2435
|
-
border
|
|
2458
|
+
background: color-mix(in srgb, var(--text-muted) 8%, transparent);
|
|
2459
|
+
border: 1px solid color-mix(in srgb, var(--border) 55%, transparent);
|
|
2460
|
+
border-radius: 6px;
|
|
2436
2461
|
padding: 8px 10px;
|
|
2437
2462
|
}
|
|
2438
2463
|
.expand-toggle-btn {
|
|
@@ -2546,7 +2571,7 @@ body::before {
|
|
|
2546
2571
|
.msg-waiting-discard:hover {
|
|
2547
2572
|
/* biome-ignore lint/complexity/noImportantStyles: override parent hover opacity */
|
|
2548
2573
|
opacity: 1 !important;
|
|
2549
|
-
color: var(--danger
|
|
2574
|
+
color: var(--danger);
|
|
2550
2575
|
}
|
|
2551
2576
|
.msg-waiting .msg-text {
|
|
2552
2577
|
font-weight: 500;
|
|
@@ -2666,7 +2691,7 @@ body::before {
|
|
|
2666
2691
|
display: inline-block;
|
|
2667
2692
|
padding: 2px 10px;
|
|
2668
2693
|
border-radius: 4px;
|
|
2669
|
-
background: var(--bg-
|
|
2694
|
+
background: var(--bg-surface);
|
|
2670
2695
|
color: var(--text-secondary);
|
|
2671
2696
|
font-size: 0.8rem;
|
|
2672
2697
|
font-weight: 600;
|
|
@@ -2706,7 +2731,7 @@ body::before {
|
|
|
2706
2731
|
left: 50%;
|
|
2707
2732
|
transform: translateX(-50%);
|
|
2708
2733
|
background: var(--accent);
|
|
2709
|
-
color: var(--bg);
|
|
2734
|
+
color: var(--bg-deep);
|
|
2710
2735
|
border: none;
|
|
2711
2736
|
border-radius: 16px;
|
|
2712
2737
|
padding: 6px 16px;
|
|
@@ -2722,7 +2747,7 @@ body::before {
|
|
|
2722
2747
|
.msg-loading-more {
|
|
2723
2748
|
text-align: center;
|
|
2724
2749
|
padding: 8px;
|
|
2725
|
-
color: var(--text-
|
|
2750
|
+
color: var(--text-muted);
|
|
2726
2751
|
font-size: 12px;
|
|
2727
2752
|
}
|
|
2728
2753
|
.msg-limit-banner {
|
|
@@ -2783,18 +2808,28 @@ body::before {
|
|
|
2783
2808
|
flex-direction: column;
|
|
2784
2809
|
gap: 3px;
|
|
2785
2810
|
padding: 8px 12px;
|
|
2786
|
-
background: var(--bg-
|
|
2787
|
-
border: 1px solid var(--border);
|
|
2811
|
+
background: var(--bg-deep);
|
|
2812
|
+
border: 1px solid color-mix(in srgb, var(--border) 55%, transparent);
|
|
2788
2813
|
border-radius: 8px;
|
|
2789
2814
|
white-space: nowrap;
|
|
2790
2815
|
min-width: 0;
|
|
2791
2816
|
overflow: hidden;
|
|
2792
|
-
transition:
|
|
2817
|
+
transition:
|
|
2818
|
+
opacity 0.3s,
|
|
2819
|
+
border-color 0.15s ease;
|
|
2793
2820
|
cursor: pointer;
|
|
2794
2821
|
position: relative;
|
|
2795
2822
|
}
|
|
2796
2823
|
.agent-card:hover {
|
|
2824
|
+
border-color: color-mix(in srgb, var(--accent) 60%, var(--border));
|
|
2825
|
+
}
|
|
2826
|
+
.agent-card.selected,
|
|
2827
|
+
.agent-card.selected:hover {
|
|
2828
|
+
background: var(--bg-elevated);
|
|
2797
2829
|
border-color: var(--accent);
|
|
2830
|
+
box-shadow:
|
|
2831
|
+
0 0 0 1px var(--accent-dim),
|
|
2832
|
+
0 0 12px var(--accent-dim);
|
|
2798
2833
|
}
|
|
2799
2834
|
.agent-card.fading {
|
|
2800
2835
|
opacity: 0.4;
|
|
@@ -2867,7 +2902,7 @@ body::before {
|
|
|
2867
2902
|
.agent-dismiss-btn {
|
|
2868
2903
|
font-size: 12px;
|
|
2869
2904
|
padding: 4px 10px;
|
|
2870
|
-
background: var(--bg-
|
|
2905
|
+
background: var(--bg-elevated);
|
|
2871
2906
|
color: var(--text-secondary);
|
|
2872
2907
|
border: 1px solid var(--border);
|
|
2873
2908
|
}
|
|
@@ -2983,10 +3018,10 @@ body::before {
|
|
|
2983
3018
|
/* #region LIGHT_THEME */
|
|
2984
3019
|
body.light {
|
|
2985
3020
|
--bg-deep: #e8e6e3;
|
|
2986
|
-
--bg-surface: #
|
|
2987
|
-
--bg-elevated: #
|
|
3021
|
+
--bg-surface: #efede9;
|
|
3022
|
+
--bg-elevated: #fbfaf9;
|
|
2988
3023
|
--bg-hover: #d2d0cc;
|
|
2989
|
-
--border: #
|
|
3024
|
+
--border: #cfcbc4;
|
|
2990
3025
|
--text-primary: #0a0a0a;
|
|
2991
3026
|
--text-secondary: #444444;
|
|
2992
3027
|
--text-tertiary: #666666;
|
|
@@ -2994,10 +3029,13 @@ body.light {
|
|
|
2994
3029
|
--accent-text: #b85a20;
|
|
2995
3030
|
--accent-dim: rgba(232, 111, 51, 0.18);
|
|
2996
3031
|
--accent-glow: rgba(232, 111, 51, 0.5);
|
|
3032
|
+
--gold: #a8842f;
|
|
2997
3033
|
--success: #1a8a5a;
|
|
2998
3034
|
--success-dim: rgba(26, 138, 90, 0.15);
|
|
2999
3035
|
--warning: #b07d0a;
|
|
3000
3036
|
--warning-dim: rgba(176, 125, 10, 0.15);
|
|
3037
|
+
--danger: #c0392b;
|
|
3038
|
+
--danger-dim: rgba(192, 57, 43, 0.15);
|
|
3001
3039
|
--plan: #5a7a5a;
|
|
3002
3040
|
--plan-dim: rgba(90, 122, 90, 0.15);
|
|
3003
3041
|
}
|
|
@@ -3126,7 +3164,9 @@ body.light .msg-assistant .msg-text {
|
|
|
3126
3164
|
max-width: 860px;
|
|
3127
3165
|
max-height: 90vh;
|
|
3128
3166
|
padding: 24px;
|
|
3129
|
-
box-shadow:
|
|
3167
|
+
box-shadow:
|
|
3168
|
+
0 16px 44px rgba(0, 0, 0, 0.38),
|
|
3169
|
+
0 4px 12px rgba(0, 0, 0, 0.2);
|
|
3130
3170
|
overflow-y: auto;
|
|
3131
3171
|
}
|
|
3132
3172
|
|
|
@@ -3209,7 +3249,7 @@ body.light .msg-assistant .msg-text {
|
|
|
3209
3249
|
}
|
|
3210
3250
|
|
|
3211
3251
|
.preview-fm .fm-v {
|
|
3212
|
-
color: var(--text
|
|
3252
|
+
color: var(--text-primary);
|
|
3213
3253
|
word-break: break-word;
|
|
3214
3254
|
white-space: pre-wrap;
|
|
3215
3255
|
}
|
|
@@ -3397,13 +3437,14 @@ select.form-input option:checked {
|
|
|
3397
3437
|
}
|
|
3398
3438
|
|
|
3399
3439
|
.btn-secondary {
|
|
3400
|
-
background:
|
|
3401
|
-
color: var(--text-
|
|
3440
|
+
background: transparent;
|
|
3441
|
+
color: var(--text-secondary);
|
|
3402
3442
|
border: 1px solid var(--border);
|
|
3403
3443
|
}
|
|
3404
3444
|
|
|
3405
3445
|
.btn-secondary:hover {
|
|
3406
3446
|
background: var(--bg-hover);
|
|
3447
|
+
color: var(--text-primary);
|
|
3407
3448
|
border-color: var(--text-muted);
|
|
3408
3449
|
}
|
|
3409
3450
|
|
|
@@ -3434,7 +3475,7 @@ select.form-input option:checked {
|
|
|
3434
3475
|
display: flex;
|
|
3435
3476
|
flex-direction: column;
|
|
3436
3477
|
align-items: center;
|
|
3437
|
-
background: var(--bg-
|
|
3478
|
+
background: var(--bg-surface);
|
|
3438
3479
|
border: 1px solid var(--border);
|
|
3439
3480
|
border-radius: 8px;
|
|
3440
3481
|
padding: 10px 16px;
|
|
@@ -3449,7 +3490,7 @@ select.form-input option:checked {
|
|
|
3449
3490
|
}
|
|
3450
3491
|
|
|
3451
3492
|
.tool-stats-chip-val.failed {
|
|
3452
|
-
color: var(--
|
|
3493
|
+
color: var(--danger);
|
|
3453
3494
|
}
|
|
3454
3495
|
|
|
3455
3496
|
.tool-stats-chip-lbl {
|
|
@@ -3490,14 +3531,14 @@ select.form-input option:checked {
|
|
|
3490
3531
|
}
|
|
3491
3532
|
|
|
3492
3533
|
.tool-stats-table tbody tr:nth-child(even) {
|
|
3493
|
-
background: var(--bg-
|
|
3534
|
+
background: var(--bg-surface);
|
|
3494
3535
|
}
|
|
3495
3536
|
|
|
3496
3537
|
.tool-stats-table tbody td {
|
|
3497
3538
|
padding: 6px 10px;
|
|
3498
3539
|
text-align: right;
|
|
3499
3540
|
color: var(--text-primary);
|
|
3500
|
-
border-bottom: 1px solid var(--border
|
|
3541
|
+
border-bottom: 1px solid var(--border);
|
|
3501
3542
|
}
|
|
3502
3543
|
|
|
3503
3544
|
.tool-stats-table tbody td.tool-name {
|
|
@@ -3551,7 +3592,7 @@ select.form-input option:checked {
|
|
|
3551
3592
|
width: 48px;
|
|
3552
3593
|
height: 6px;
|
|
3553
3594
|
border-radius: 3px;
|
|
3554
|
-
background: var(--bg-hover
|
|
3595
|
+
background: var(--bg-hover);
|
|
3555
3596
|
flex-shrink: 0;
|
|
3556
3597
|
overflow: hidden;
|
|
3557
3598
|
}
|
|
@@ -3584,11 +3625,11 @@ select.form-input option:checked {
|
|
|
3584
3625
|
max-height: 50vh;
|
|
3585
3626
|
resize: vertical;
|
|
3586
3627
|
padding: 12px;
|
|
3587
|
-
font-family:
|
|
3628
|
+
font-family: var(--mono);
|
|
3588
3629
|
font-size: 13px;
|
|
3589
3630
|
line-height: 1.6;
|
|
3590
3631
|
color: var(--text-primary);
|
|
3591
|
-
background: var(--bg-
|
|
3632
|
+
background: var(--bg-deep);
|
|
3592
3633
|
border: 1px solid var(--border);
|
|
3593
3634
|
border-radius: 8px;
|
|
3594
3635
|
outline: none;
|
|
@@ -3842,20 +3883,23 @@ select.form-input option:checked {
|
|
|
3842
3883
|
@media (prefers-color-scheme: light) {
|
|
3843
3884
|
body:not(.dark-forced) {
|
|
3844
3885
|
--bg-deep: #e8e6e3;
|
|
3845
|
-
--bg-surface: #
|
|
3846
|
-
--bg-elevated: #
|
|
3886
|
+
--bg-surface: #efede9;
|
|
3887
|
+
--bg-elevated: #fbfaf9;
|
|
3847
3888
|
--bg-hover: #d2d0cc;
|
|
3848
|
-
--border: #
|
|
3889
|
+
--border: #cfcbc4;
|
|
3849
3890
|
--text-primary: #0a0a0a;
|
|
3850
3891
|
--text-secondary: #444444;
|
|
3851
3892
|
--text-tertiary: #666666;
|
|
3852
3893
|
--text-muted: #888888;
|
|
3853
3894
|
--accent-dim: rgba(232, 111, 51, 0.18);
|
|
3854
3895
|
--accent-glow: rgba(232, 111, 51, 0.5);
|
|
3896
|
+
--gold: #a8842f;
|
|
3855
3897
|
--success: #1a8a5a;
|
|
3856
3898
|
--success-dim: rgba(26, 138, 90, 0.15);
|
|
3857
3899
|
--warning: #b07d0a;
|
|
3858
3900
|
--warning-dim: rgba(176, 125, 10, 0.15);
|
|
3901
|
+
--danger: #c0392b;
|
|
3902
|
+
--danger-dim: rgba(192, 57, 43, 0.15);
|
|
3859
3903
|
--plan: #5a7a5a;
|
|
3860
3904
|
--plan-dim: rgba(90, 122, 90, 0.15);
|
|
3861
3905
|
}
|
|
@@ -4197,8 +4241,8 @@ pre.mermaid svg {
|
|
|
4197
4241
|
.session-item.kb-selected:hover,
|
|
4198
4242
|
.session-item.active.kb-selected {
|
|
4199
4243
|
background: var(--bg-elevated);
|
|
4200
|
-
border-color: var(--
|
|
4201
|
-
box-shadow: 0 0 0 1px var(--
|
|
4244
|
+
border-color: var(--accent);
|
|
4245
|
+
box-shadow: 0 0 0 1px var(--accent-dim);
|
|
4202
4246
|
}
|
|
4203
4247
|
|
|
4204
4248
|
.session-item.kb-selected::before {
|
package/server.js
CHANGED
|
@@ -22,6 +22,7 @@ const {
|
|
|
22
22
|
extractModelFromTranscript,
|
|
23
23
|
readFullToolResult,
|
|
24
24
|
readUserImage,
|
|
25
|
+
readCachedImage,
|
|
25
26
|
updateLoopInfo,
|
|
26
27
|
buildLoopInfoFromState
|
|
27
28
|
} = require('./lib/parsers');
|
|
@@ -1912,6 +1913,14 @@ app.get('/api/sessions/:sessionId/user-image/:msgUuid/:blockIndex', (req, res) =
|
|
|
1912
1913
|
res.end(buf);
|
|
1913
1914
|
});
|
|
1914
1915
|
|
|
1916
|
+
app.get('/api/sessions/:sessionId/cached-image/:n', (req, res) => {
|
|
1917
|
+
const img = readCachedImage(req.params.sessionId, req.params.n);
|
|
1918
|
+
if (!img) return res.status(404).end();
|
|
1919
|
+
res.setHeader('Content-Type', img.mediaType);
|
|
1920
|
+
res.setHeader('Cache-Control', 'no-store');
|
|
1921
|
+
res.end(img.buffer);
|
|
1922
|
+
});
|
|
1923
|
+
|
|
1915
1924
|
app.get('/api/version', (req, res) => {
|
|
1916
1925
|
const pkg = require('./package.json');
|
|
1917
1926
|
res.json({ version: pkg.version });
|