fix: stabilize session list and Codex imports

This commit is contained in:
shiyue
2026-07-30 18:06:39 +08:00
parent 21b10e3eb4
commit cc600bdf31
13 changed files with 1247 additions and 42 deletions

View File

@@ -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) {

View File

@@ -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>