fix: stabilize session list and Codex imports
This commit is contained in:
133
public/app.js
133
public/app.js
@@ -246,6 +246,7 @@
|
||||
let isReloadingMcp = false;
|
||||
const mcpStartupToastKeys = new Map();
|
||||
let sessionSearchQuery = '';
|
||||
let lastSessionListStructureSignature = '';
|
||||
const collapsedProjectKeys = (() => {
|
||||
try {
|
||||
const parsed = JSON.parse(localStorage.getItem(PROJECT_COLLAPSE_STORAGE_KEY) || '[]');
|
||||
@@ -8637,8 +8638,58 @@
|
||||
updateScrollbar();
|
||||
|
||||
|
||||
function buildSessionListStructureSignature(model) {
|
||||
const sessionSignature = (session) => ({
|
||||
id: String(session?.id || ''),
|
||||
agent: String(session?.agent || ''),
|
||||
title: String(session?.title || ''),
|
||||
projectName: getSessionProjectName(session),
|
||||
cwd: getSessionEffectiveCwd(session),
|
||||
active: session?.id === currentSessionId,
|
||||
pinnedAt: session?.pinnedAt || '',
|
||||
createdFromKind: String(session?.createdFromKind || '').toLowerCase(),
|
||||
isRunning: !!session?.isRunning,
|
||||
hasUnread: !!session?.hasUnread,
|
||||
waitingOnChildren: !!session?.waitingOnChildren,
|
||||
readyReplyCount: Number(session?.readyReplyCount || 0),
|
||||
pendingReplyCount: Number(session?.pendingReplyCount || 0),
|
||||
});
|
||||
return JSON.stringify({
|
||||
agent: currentAgent,
|
||||
currentSessionId,
|
||||
search: model.normalizedSearchQuery,
|
||||
allEmpty: model.allVisibleSessions.length === 0,
|
||||
visibleEmpty: model.visibleSessions.length === 0,
|
||||
pinned: model.pinnedSessions.map(sessionSignature),
|
||||
groups: model.projectGroups.map((entry) => ({
|
||||
key: entry.groupKey,
|
||||
oldSessionCollapseKey: entry.oldSessionCollapseKey,
|
||||
name: entry.group.name,
|
||||
cwd: entry.group.cwd || '',
|
||||
count: entry.group.sessions.length,
|
||||
collapsed: entry.isCollapsed,
|
||||
hiddenCount: entry.hiddenGroupSessions.length,
|
||||
visible: entry.visibleGroupSessions.map(sessionSignature),
|
||||
hidden: entry.hiddenGroupSessions.map(sessionSignature),
|
||||
})),
|
||||
ungroupedCollapseKey: model.ungroupedCollapseKey,
|
||||
hiddenUngroupedCount: model.hiddenUngroupedSessions.length,
|
||||
ungrouped: model.visibleUngroupedSessions.map(sessionSignature),
|
||||
hiddenUngrouped: model.hiddenUngroupedSessions.map(sessionSignature),
|
||||
});
|
||||
}
|
||||
|
||||
function refreshSessionListRelativeTimes(visibleSessions) {
|
||||
const sessionsById = new Map(visibleSessions.map((session) => [String(session.id || ''), session]));
|
||||
sessionList.querySelectorAll('.session-item').forEach((item) => {
|
||||
const session = sessionsById.get(String(item.dataset.id || ''));
|
||||
if (!session) return;
|
||||
const timeEl = item.querySelector('.session-item-time');
|
||||
if (timeEl) timeEl.textContent = timeAgo(session.updated);
|
||||
});
|
||||
}
|
||||
|
||||
function renderSessionList() {
|
||||
sessionList.innerHTML = '';
|
||||
syncSessionSearchUi();
|
||||
const allVisibleSessions = getVisibleSessions();
|
||||
const normalizedSearchQuery = normalizeSessionSearchQuery(sessionSearchQuery);
|
||||
@@ -8646,11 +8697,56 @@
|
||||
const visibleSessions = isSearchingSessions
|
||||
? allVisibleSessions.filter((session) => sessionMatchesSearch(session, normalizedSearchQuery))
|
||||
: allVisibleSessions;
|
||||
const { pinnedSessions, regularSessions } = splitPinnedSessions(visibleSessions);
|
||||
const { groups: projectGroups, ungroupedSessions } = groupSessionsByProject(regularSessions);
|
||||
const renderGroups = projectGroups.map((group, groupIndex) => {
|
||||
const groupKey = getProjectCollapseKey(group);
|
||||
const oldSessionCollapseKey = getProjectOldSessionCollapseKey(group);
|
||||
const { visibleSessions: visibleGroupSessions, hiddenSessions: hiddenGroupSessions } = isSearchingSessions
|
||||
? { visibleSessions: group.sessions, hiddenSessions: [] }
|
||||
: splitCollapsedSessions(group.sessions, oldSessionCollapseKey);
|
||||
const isCollapsed = !isSearchingSessions && collapsedProjectKeys.has(groupKey);
|
||||
return {
|
||||
group,
|
||||
groupIndex,
|
||||
groupKey,
|
||||
oldSessionCollapseKey,
|
||||
visibleGroupSessions,
|
||||
hiddenGroupSessions,
|
||||
isCollapsed,
|
||||
hasActiveSession: group.sessions.some((session) => session.id === currentSessionId),
|
||||
hasUnreadSession: group.sessions.some((session) => session.hasUnread),
|
||||
hasRunningSession: group.sessions.some((session) => session.isRunning),
|
||||
hasWaitingSession: group.sessions.some((session) => session.waitingOnChildren),
|
||||
groupBodyId: `session-project-body-${groupIndex}`,
|
||||
};
|
||||
});
|
||||
const ungroupedCollapseKey = getUngroupedOldSessionCollapseKey();
|
||||
const { visibleSessions: visibleUngroupedSessions, hiddenSessions: hiddenUngroupedSessions } = isSearchingSessions
|
||||
? { visibleSessions: ungroupedSessions, hiddenSessions: [] }
|
||||
: splitCollapsedSessions(ungroupedSessions, ungroupedCollapseKey);
|
||||
const structureSignature = buildSessionListStructureSignature({
|
||||
normalizedSearchQuery,
|
||||
allVisibleSessions,
|
||||
visibleSessions,
|
||||
pinnedSessions,
|
||||
projectGroups: renderGroups,
|
||||
ungroupedCollapseKey,
|
||||
visibleUngroupedSessions,
|
||||
hiddenUngroupedSessions,
|
||||
});
|
||||
if (structureSignature === lastSessionListStructureSignature && sessionList.childElementCount > 0) {
|
||||
// 高频快照常只更新 updated;它只影响相对时间,不能触发节点替换。
|
||||
refreshSessionListRelativeTimes(visibleSessions);
|
||||
return;
|
||||
}
|
||||
sessionList.innerHTML = '';
|
||||
if (allVisibleSessions.length === 0) {
|
||||
const empty = document.createElement('div');
|
||||
empty.className = 'session-list-empty';
|
||||
empty.textContent = `暂无 ${AGENT_LABELS[currentAgent]} 会话,点击“新会话”开始。`;
|
||||
sessionList.appendChild(empty);
|
||||
lastSessionListStructureSignature = structureSignature;
|
||||
return;
|
||||
}
|
||||
if (visibleSessions.length === 0) {
|
||||
@@ -8658,11 +8754,10 @@
|
||||
empty.className = 'session-list-empty';
|
||||
empty.textContent = '没有匹配的会话或项目。';
|
||||
sessionList.appendChild(empty);
|
||||
lastSessionListStructureSignature = structureSignature;
|
||||
return;
|
||||
}
|
||||
|
||||
const { pinnedSessions, regularSessions } = splitPinnedSessions(visibleSessions);
|
||||
const { groups: projectGroups, ungroupedSessions } = groupSessionsByProject(regularSessions);
|
||||
if (pinnedSessions.length > 0) {
|
||||
const pinnedGroupEl = document.createElement('section');
|
||||
pinnedGroupEl.className = 'session-project-group session-pinned-group';
|
||||
@@ -8681,18 +8776,20 @@
|
||||
sessionList.appendChild(pinnedGroupEl);
|
||||
}
|
||||
|
||||
projectGroups.forEach((group, groupIndex) => {
|
||||
const groupKey = getProjectCollapseKey(group);
|
||||
const oldSessionCollapseKey = getProjectOldSessionCollapseKey(group);
|
||||
const { visibleSessions: visibleGroupSessions, hiddenSessions: hiddenGroupSessions } = isSearchingSessions
|
||||
? { visibleSessions: group.sessions, hiddenSessions: [] }
|
||||
: splitCollapsedSessions(group.sessions, oldSessionCollapseKey);
|
||||
const isCollapsed = !isSearchingSessions && collapsedProjectKeys.has(groupKey);
|
||||
const hasActiveSession = group.sessions.some((session) => session.id === currentSessionId);
|
||||
const hasUnreadSession = group.sessions.some((session) => session.hasUnread);
|
||||
const hasRunningSession = group.sessions.some((session) => session.isRunning);
|
||||
const hasWaitingSession = group.sessions.some((session) => session.waitingOnChildren);
|
||||
const groupBodyId = `session-project-body-${groupIndex}`;
|
||||
renderGroups.forEach((entry) => {
|
||||
const {
|
||||
group,
|
||||
groupKey,
|
||||
oldSessionCollapseKey,
|
||||
visibleGroupSessions,
|
||||
hiddenGroupSessions,
|
||||
isCollapsed,
|
||||
hasActiveSession,
|
||||
hasUnreadSession,
|
||||
hasRunningSession,
|
||||
hasWaitingSession,
|
||||
groupBodyId,
|
||||
} = entry;
|
||||
const groupEl = document.createElement('section');
|
||||
groupEl.className = `session-project-group${isCollapsed ? ' collapsed' : ''}${hasActiveSession ? ' has-active-session' : ''}${hasUnreadSession ? ' has-unread-session' : ''}${hasRunningSession ? ' has-running-session' : ''}${hasWaitingSession ? ' has-waiting-session' : ''}`;
|
||||
|
||||
@@ -8735,11 +8832,6 @@
|
||||
sessionList.appendChild(groupEl);
|
||||
});
|
||||
|
||||
const ungroupedCollapseKey = getUngroupedOldSessionCollapseKey();
|
||||
const { visibleSessions: visibleUngroupedSessions, hiddenSessions: hiddenUngroupedSessions } = isSearchingSessions
|
||||
? { visibleSessions: ungroupedSessions, hiddenSessions: [] }
|
||||
: splitCollapsedSessions(ungroupedSessions, ungroupedCollapseKey);
|
||||
|
||||
for (const s of visibleUngroupedSessions) {
|
||||
sessionList.appendChild(createSessionListItem(s));
|
||||
}
|
||||
@@ -8747,6 +8839,7 @@
|
||||
if (hiddenUngroupedSessions.length > 0) {
|
||||
sessionList.appendChild(createOldSessionLoadMoreButton(hiddenUngroupedSessions.length, ungroupedCollapseKey));
|
||||
}
|
||||
lastSessionListStructureSignature = structureSignature;
|
||||
}
|
||||
|
||||
function startEditSessionTitle(itemEl, session) {
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
document.documentElement.dataset.dividerTime = dividerTime;
|
||||
})();
|
||||
</script>
|
||||
<link rel="stylesheet" href="style.css?v=20260727-session-item-tooltip">
|
||||
<link rel="stylesheet" href="style.css?v=20260730-sidebar-title-refresh-storm">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/atom-one-dark.min.css">
|
||||
</head>
|
||||
<body>
|
||||
@@ -183,6 +183,6 @@
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/marked/12.0.1/marked.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/mermaid/10.9.1/mermaid.min.js"></script>
|
||||
<script src="app.js?v=20260727-session-item-tooltip"></script>
|
||||
<script src="app.js?v=20260730-sidebar-title-refresh-storm"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user