Fix project isolation: Make loadChatHistory respect active project sessions

- Modified loadChatHistory() to check for active project before fetching all sessions
- When active project exists, use project.sessions instead of fetching from API
- Added detailed console logging to debug session filtering
- This prevents ALL sessions from appearing in every project's sidebar

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
uroma
2026-01-22 14:43:05 +00:00
Unverified
parent b82837aa5f
commit 55aafbae9a
6463 changed files with 1115462 additions and 4486 deletions

View File

@@ -48,11 +48,9 @@ function enhanceChatInput() {
// Auto-load chat history when page loads
// Make this a named function so it can be called to refresh the sidebar
async function loadChatHistory() {
// @param {Array} sessionsToRender - Optional: specific sessions to render (for project filtering)
async function loadChatHistory(sessionsToRender = null) {
try {
const res = await fetch('/claude/api/claude/sessions');
const data = await res.json();
const historyList = document.getElementById('chat-history-list');
if (!historyList) return;
@@ -63,17 +61,61 @@ async function loadChatHistory() {
return;
}
// Combine active and historical sessions
const allSessions = [
...(data.active || []).map(s => ({...s, status: 'active'})),
...(data.historical || []).map(s => ({...s, status: 'historical'}))
];
let allSessions;
if (sessionsToRender) {
// Use provided sessions (for project filtering)
allSessions = sessionsToRender;
console.log('[loadChatHistory] Rendering provided sessions:', allSessions.length);
} else {
// CRITICAL FIX: If no sessions provided, check if there's an active project
// If there is, we should NOT fetch from API - instead wait for project to provide sessions
if (window.projectManager && window.projectManager.activeProjectId) {
const activeProject = window.projectManager.projects.get(
window.projectManager.activeProjectId.replace('project-', '')
);
if (activeProject) {
// Use the active project's sessions
allSessions = activeProject.sessions || [];
console.log('[loadChatHistory] Using active project sessions:', allSessions.length, 'project:', activeProject.name);
} else {
// No project found, fetch from API as fallback
const res = await fetch('/claude/api/claude/sessions');
const data = await res.json();
allSessions = [
...(data.active || []).map(s => ({...s, status: 'active'})),
...(data.historical || []).map(s => ({...s, status: 'historical'}))
];
console.log('[loadChatHistory] No active project found, loaded from API:', allSessions.length);
}
} else {
// No project manager or no active project, fetch all sessions
const res = await fetch('/claude/api/claude/sessions');
const data = await res.json();
allSessions = [
...(data.active || []).map(s => ({...s, status: 'active'})),
...(data.historical || []).map(s => ({...s, status: 'historical'}))
];
console.log('[loadChatHistory] No active project, loaded all sessions from API:', allSessions.length);
}
}
// Sort by creation date (newest first)
allSessions.sort((a, b) => new Date(b.createdAt || b.created_at) - new Date(a.createdAt || a.created_at));
// CRITICAL DEBUG: Log session details for debugging
console.log('[loadChatHistory] Total sessions to render:', allSessions.length);
allSessions.forEach((s, i) => {
console.log(`[loadChatHistory] Session ${i}:`, {
id: s.id.substring(0, 8),
workingDir: s.workingDir,
project: s.metadata?.project,
status: s.status
});
});
if (allSessions.length === 0) {
historyList.innerHTML = '<div class="chat-history-empty">No chat history yet</div>';
historyList.innerHTML = '<div class="chat-history-empty">No sessions in this project</div>';
return;
}
@@ -86,24 +128,35 @@ async function loadChatHistory() {
return `
<div class="chat-history-item ${isActive ? 'active' : ''} ${session.status === 'historical' ? 'historical' : ''}"
onclick="${session.status === 'historical' ? `resumeSession('${session.id}')` : `attachToSession('${session.id}')`}">
<div class="chat-history-icon">
${session.status === 'historical' ? '📁' : '💬'}
</div>
<div class="chat-history-content">
<div class="chat-history-title">${title}</div>
<div class="chat-history-meta">
<span class="chat-history-date">${date}</span>
<span class="chat-history-status ${session.status}">
${session.status === 'historical' ? 'Historical' : 'Active'}
</span>
data-session-id="${session.id}">
<div class="chat-history-main" onclick="${session.status === 'historical' ? `resumeSession('${session.id}')` : `attachToSession('${session.id}')`}">
<div class="chat-history-icon">
${session.status === 'historical' ? '📁' : '💬'}
</div>
<div class="chat-history-content">
<div class="chat-history-title">${title}</div>
<div class="chat-history-meta">
<span class="chat-history-date">${date}</span>
<span class="chat-history-status ${session.status}">
${session.status === 'historical' ? 'Historical' : 'Active'}
</span>
</div>
</div>
${session.status === 'historical' ? '<span class="resume-badge">Resume</span>' : ''}
</div>
${session.status === 'historical' ? '<span class="resume-badge">Resume</span>' : ''}
<button class="chat-history-archive" onclick="event.stopPropagation(); archiveSession('${session.id}')" title="Archive session">
📦
</button>
</div>
`;
}).join('');
// CRITICAL FIX: Also update session tabs with the same sessions
if (window.sessionTabs && typeof window.sessionTabs.setSessions === 'function') {
window.sessionTabs.setSessions(allSessions);
console.log('[loadChatHistory] Updated session tabs with', allSessions.length, 'sessions');
}
} catch (error) {
console.error('[loadChatHistory] Error loading chat history:', error);
}
@@ -157,7 +210,10 @@ async function resumeSession(sessionId) {
throw new Error('Invalid JSON response from server');
}
if (data.session) {
// CRITICAL FIX: API returns session directly, not wrapped in {session: ...}
const session = data.session || data;
if (session && session.id) {
if (typeof attachToSession === 'function') {
attachToSession(sessionId);
}
@@ -172,8 +228,8 @@ async function resumeSession(sessionId) {
}
// Add historical messages
if (data.session.outputBuffer && data.session.outputBuffer.length > 0) {
data.session.outputBuffer.forEach(entry => {
if (session.outputBuffer && session.outputBuffer.length > 0) {
session.outputBuffer.forEach(entry => {
if (typeof appendMessage === 'function') {
appendMessage('assistant', entry.content, false);
}
@@ -181,7 +237,7 @@ async function resumeSession(sessionId) {
}
// Show resume message
const sessionDate = new Date(data.session.createdAt || data.session.created_at);
const sessionDate = new Date(session.createdAt || session.created_at);
if (typeof appendSystemMessage === 'function') {
appendSystemMessage('✅ Resumed historical session from ' + sessionDate.toLocaleString());
appendSystemMessage(' This is a read-only historical session. Start a new chat to continue working.');
@@ -449,6 +505,251 @@ if (document.readyState === 'loading') {
}, 1500);
}
// ============================================
// Archive & Merge Sessions
// ============================================
// Track selected sessions for merge
window.selectedSessionsForMerge = new Set();
/**
* Archive a session
*/
async function archiveSession(sessionId) {
console.log('[Archive] Archiving session:', sessionId);
const confirmed = confirm('Archive this session? It will be hidden from the main list but can be unarchived later.');
if (!confirmed) return;
try {
const res = await fetch(`/claude/api/claude/sessions/${sessionId}/archive`, {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' }
});
if (!res.ok) {
// Try to get more error details
let errorMessage = 'Failed to archive session';
try {
const errorData = await res.json();
errorMessage = errorData.message || errorData.error || errorMessage;
} catch (e) {
errorMessage = `HTTP ${res.status}: ${res.statusText}`;
}
throw new Error(errorMessage);
}
// Refresh the session list
if (typeof loadChatHistory === 'function') {
await loadChatHistory();
}
// Show success message
if (typeof appendSystemMessage === 'function') {
appendSystemMessage('✅ Session archived successfully');
}
} catch (error) {
console.error('[Archive] Error:', error);
if (typeof appendSystemMessage === 'function') {
appendSystemMessage('❌ Failed to archive session: ' + error.message);
}
}
}
/**
* Toggle session selection for merge
*/
function toggleSessionSelection(sessionId) {
if (window.selectedSessionsForMerge.has(sessionId)) {
window.selectedSessionsForMerge.delete(sessionId);
} else {
window.selectedSessionsForMerge.add(sessionId);
}
// Update UI
const item = document.querySelector(`[data-session-id="${sessionId}"]`);
if (item) {
item.classList.toggle('selected-for-merge', window.selectedSessionsForMerge.has(sessionId));
}
// Show/hide merge button
updateMergeButtonVisibility();
}
/**
* Update merge button visibility based on selection
*/
function updateMergeButtonVisibility() {
const mergeBtn = document.getElementById('merge-sessions-btn');
if (!mergeBtn) return;
if (window.selectedSessionsForMerge.size >= 2) {
mergeBtn.style.display = 'flex';
mergeBtn.textContent = `🔀 Emerge ${window.selectedSessionsForMerge.size} Sessions`;
} else {
mergeBtn.style.display = 'none';
}
}
/**
* Merge selected sessions
*/
async function mergeSessions() {
const sessionIds = Array.from(window.selectedSessionsForMerge);
if (sessionIds.length < 2) {
alert('Please select at least 2 sessions to merge');
return;
}
console.log('[Merge] Merging sessions:', sessionIds);
const confirmed = confirm(`Merge ${sessionIds.length} sessions into one? This will create a new session with all messages from the selected sessions.`);
if (!confirmed) return;
try {
if (typeof appendSystemMessage === 'function') {
appendSystemMessage('🔀 Merging sessions...');
}
const res = await fetch('/claude/api/claude/sessions/merge', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ sessionIds })
});
if (!res.ok) throw new Error('Failed to merge sessions');
const data = await res.json();
if (data.success && data.session) {
// Clear selection
window.selectedSessionsForMerge.clear();
updateMergeButtonVisibility();
// Remove all selected classes
document.querySelectorAll('.selected-for-merge').forEach(el => {
el.classList.remove('selected-for-merge');
});
// Refresh the session list
if (typeof loadChatHistory === 'function') {
await loadChatHistory();
}
// Attach to the new merged session
if (typeof attachToSession === 'function') {
await attachToSession(data.session.id);
}
if (typeof appendSystemMessage === 'function') {
appendSystemMessage('✅ Sessions merged successfully!');
}
}
} catch (error) {
console.error('[Merge] Error:', error);
if (typeof appendSystemMessage === 'function') {
appendSystemMessage('❌ Failed to merge sessions: ' + error.message);
}
}
}
/**
* Show archived sessions view
*/
async function showArchivedSessions() {
console.log('[Archive] Loading archived sessions...');
try {
const res = await fetch('/claude/api/claude/sessions?archived=true');
const data = await res.json();
const archivedSessions = data.archived || [];
const historyList = document.getElementById('chat-history-list');
if (!historyList) return;
if (archivedSessions.length === 0) {
historyList.innerHTML = `
<div class="chat-history-empty">
<div>No archived sessions</div>
<button onclick="loadChatHistory()" style="margin-top: 12px; padding: 8px 16px; background: #333; border: none; border-radius: 6px; color: #fff; cursor: pointer;">
← Back to Sessions
</button>
</div>
`;
return;
}
// Update header to show back button
const historyHeader = document.querySelector('.chat-history-header h3');
if (historyHeader) {
historyHeader.innerHTML = `
<button onclick="loadChatHistory()" style="background: none; border: none; color: #888; cursor: pointer; font-size: 16px;">
← Back
</button>
<span style="margin-left: 8px;">Archived Sessions</span>
`;
}
historyList.innerHTML = archivedSessions.map(session => {
const title = session.metadata?.project ||
session.workingDir?.split('/').pop() ||
session.id.substring(0, 12) + '...';
const archivedDate = new Date(session.archivedAt).toLocaleDateString();
return `
<div class="chat-history-item historical" data-session-id="${session.id}">
<div class="chat-history-main" onclick="resumeSession('${session.id}')">
<div class="chat-history-icon">📦</div>
<div class="chat-history-content">
<div class="chat-history-title">${title}</div>
<div class="chat-history-meta">
<span class="chat-history-date">Archived: ${archivedDate}</span>
</div>
</div>
</div>
<button class="chat-history-unarchive" onclick="event.stopPropagation(); unarchiveSession('${session.id}')" title="Unarchive session">
📤
</button>
</div>
`;
}).join('');
} catch (error) {
console.error('[Archive] Error loading archived sessions:', error);
}
}
/**
* Unarchive a session
*/
async function unarchiveSession(sessionId) {
console.log('[Archive] Unarchiving session:', sessionId);
try {
const res = await fetch(`/claude/api/claude/sessions/${sessionId}/unarchive`, {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' }
});
if (!res.ok) throw new Error('Failed to unarchive session');
// Refresh the archived list
await showArchivedSessions();
// Show success message
if (typeof appendSystemMessage === 'function') {
appendSystemMessage('✅ Session unarchived successfully');
}
} catch (error) {
console.error('[Archive] Error:', error);
if (typeof appendSystemMessage === 'function') {
appendSystemMessage('❌ Failed to unarchive session: ' + error.message);
}
}
}
// Export functions
if (typeof window !== 'undefined') {
window.resumeSession = resumeSession;
@@ -457,4 +758,9 @@ if (typeof window !== 'undefined') {
window.enhanceChatInput = enhanceChatInput;
window.focusChatInput = focusChatInput;
window.appendMessageWithAnimation = appendMessageWithAnimation;
window.archiveSession = archiveSession;
window.toggleSessionSelection = toggleSessionSelection;
window.mergeSessions = mergeSessions;
window.showArchivedSessions = showArchivedSessions;
window.unarchiveSession = unarchiveSession;
}