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:
@@ -24,6 +24,7 @@ class ProjectManager {
|
||||
this.initialized = false;
|
||||
this.closedProjects = new Set(); // Track closed project IDs
|
||||
this.STORAGE_KEY = 'claude_ide_closed_projects';
|
||||
this.PROJECTS_STORAGE_KEY = 'claude_ide_projects'; // Store manually created projects
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -34,7 +35,8 @@ class ProjectManager {
|
||||
|
||||
console.log('[ProjectManager] Initializing...');
|
||||
this.loadClosedProjects();
|
||||
await this.loadProjects();
|
||||
this.loadManuallyCreatedProjects(); // Load manually created projects first
|
||||
await this.loadProjects(); // Then load from sessions
|
||||
this.renderProjectTabs();
|
||||
this.initialized = true;
|
||||
|
||||
@@ -47,6 +49,54 @@ class ProjectManager {
|
||||
console.log('[ProjectManager] Initialized with', this.projects.size, 'projects');
|
||||
}
|
||||
|
||||
/**
|
||||
* Load manually created projects from localStorage
|
||||
*/
|
||||
loadManuallyCreatedProjects() {
|
||||
try {
|
||||
const stored = localStorage.getItem(this.PROJECTS_STORAGE_KEY);
|
||||
console.log('[ProjectManager] Checking localStorage for projects...');
|
||||
console.log('[ProjectManager] Storage key:', this.PROJECTS_STORAGE_KEY);
|
||||
console.log('[ProjectManager] Found data:', stored ? 'YES' : 'NO');
|
||||
|
||||
if (stored) {
|
||||
const projectsData = JSON.parse(stored);
|
||||
console.log('[ProjectManager] Loading', projectsData.length, 'manually created projects from storage');
|
||||
console.log('[ProjectManager] Projects data:', projectsData);
|
||||
|
||||
projectsData.forEach(projectData => {
|
||||
const projectKey = projectData.id.replace('project-', '');
|
||||
this.projects.set(projectKey, projectData);
|
||||
console.log('[ProjectManager] Loaded project:', projectData.name, 'with', projectData.sessions.length, 'sessions');
|
||||
});
|
||||
} else {
|
||||
console.log('[ProjectManager] No manually created projects found in storage');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[ProjectManager] Error loading manually created projects:', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Save manually created projects to localStorage
|
||||
*/
|
||||
saveManuallyCreatedProjects() {
|
||||
try {
|
||||
// Only save projects that were manually created (not auto-generated from workingDir)
|
||||
const manuallyCreatedProjects = Array.from(this.projects.values())
|
||||
.filter(p => p.manuallyCreated === true);
|
||||
|
||||
const dataToStore = JSON.stringify(manuallyCreatedProjects);
|
||||
localStorage.setItem(this.PROJECTS_STORAGE_KEY, dataToStore);
|
||||
console.log('[ProjectManager] Saved', manuallyCreatedProjects.length, 'manually created projects to storage');
|
||||
console.log('[ProjectManager] Stored data:', dataToStore);
|
||||
} catch (error) {
|
||||
console.error('[ProjectManager] Error saving manually created projects:', error);
|
||||
console.error('[ProjectManager] localStorage available:', typeof localStorage !== 'undefined');
|
||||
console.error('[ProjectManager] Storage key:', this.PROJECTS_STORAGE_KEY);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load closed projects from localStorage
|
||||
*/
|
||||
@@ -94,12 +144,23 @@ class ProjectManager {
|
||||
];
|
||||
|
||||
// Group by working directory
|
||||
// CRITICAL FIX: Handle virtual projects by adding sessions directly to manually created projects
|
||||
const virtualSessions = []; // Store sessions with virtual workingDirs
|
||||
|
||||
const grouped = new Map();
|
||||
|
||||
console.log('[ProjectManager] Processing', allSessions.length, 'total sessions');
|
||||
|
||||
allSessions.forEach(session => {
|
||||
const dir = session.workingDir || 'default';
|
||||
const projectKey = dir.replace(/\//g, '-').replace(/^-/, '') || 'default';
|
||||
|
||||
// Check if this is a virtual workingDir
|
||||
if (dir.startsWith('/virtual/projects/')) {
|
||||
virtualSessions.push(session);
|
||||
return; // Don't add to grouped, will handle in manually created projects
|
||||
}
|
||||
|
||||
if (!grouped.has(projectKey)) {
|
||||
const projectName = dir.split('/').pop() || 'Default';
|
||||
const project = {
|
||||
@@ -116,6 +177,8 @@ class ProjectManager {
|
||||
grouped.get(projectKey).sessions.push(session);
|
||||
});
|
||||
|
||||
console.log('[ProjectManager] Separated', virtualSessions.length, 'virtual sessions and', grouped.size, 'real projects');
|
||||
|
||||
// Sort sessions by last activity within each project
|
||||
grouped.forEach(project => {
|
||||
project.sessions.sort((a, b) => {
|
||||
@@ -138,6 +201,52 @@ class ProjectManager {
|
||||
}
|
||||
});
|
||||
|
||||
// CRITICAL FIX: Merge with existing manually created projects
|
||||
// Add virtual sessions to their corresponding manually created projects
|
||||
const manuallyCreated = Array.from(this.projects.entries())
|
||||
.filter(([key, p]) => p.manuallyCreated === true);
|
||||
|
||||
manuallyCreated.forEach(([key, manualProject]) => {
|
||||
if (!filtered.has(key)) {
|
||||
// Project doesn't exist in filtered, just add it
|
||||
filtered.set(key, manualProject);
|
||||
console.log('[ProjectManager] Preserving manually created project:', manualProject.name);
|
||||
} else {
|
||||
// Project exists in filtered - for virtual projects, prefer manually created version
|
||||
if (manualProject.isVirtual) {
|
||||
// Replace with manually created version (which has correct name, etc.)
|
||||
filtered.set(key, manualProject);
|
||||
}
|
||||
}
|
||||
|
||||
// Add virtual sessions that belong to this project
|
||||
const projectVirtualSessions = virtualSessions.filter(s => {
|
||||
const sessionProjectKey = s.workingDir?.replace('/virtual/projects/', '') || '';
|
||||
return sessionProjectKey === key;
|
||||
});
|
||||
|
||||
if (projectVirtualSessions.length > 0) {
|
||||
console.log('[ProjectManager] Found', projectVirtualSessions.length, 'virtual sessions for project:', manualProject.name, 'key:', key);
|
||||
const existingSessionIds = new Set(manualProject.sessions.map(s => s.id));
|
||||
projectVirtualSessions.forEach(session => {
|
||||
if (!existingSessionIds.has(session.id)) {
|
||||
manualProject.sessions.push(session);
|
||||
console.log('[ProjectManager] Added session', session.id, 'to virtual project:', manualProject.name);
|
||||
}
|
||||
});
|
||||
// Sort sessions
|
||||
manualProject.sessions.sort((a, b) => {
|
||||
const dateA = new Date(a.lastActivity || a.createdAt || a.created_at || 0);
|
||||
const dateB = new Date(b.lastActivity || b.createdAt || b.created_at || 0);
|
||||
return dateB - dateA;
|
||||
});
|
||||
// Update active session
|
||||
if (manualProject.sessions.length > 0) {
|
||||
manualProject.activeSessionId = manualProject.sessions[0].id;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
this.projects = filtered;
|
||||
console.log('[ProjectManager] Loaded', this.projects.size, 'projects (filtered out', grouped.size - this.projects.size, 'closed)');
|
||||
|
||||
@@ -245,12 +354,17 @@ class ProjectManager {
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('[ProjectManager] Switching to project:', project.name);
|
||||
console.log('[ProjectManager] Switching to project:', project.name, 'with', project.sessions.length, 'sessions');
|
||||
this.activeProjectId = project.id;
|
||||
|
||||
// Re-render project tabs to update active state
|
||||
this.renderProjectTabs();
|
||||
|
||||
// CRITICAL FIX: Update left sidebar chat history with this project's sessions
|
||||
if (typeof loadChatHistory === 'function') {
|
||||
await loadChatHistory(project.sessions);
|
||||
}
|
||||
|
||||
// Update session tabs for this project
|
||||
if (window.sessionTabs) {
|
||||
window.sessionTabs.setSessions(project.sessions);
|
||||
@@ -290,56 +404,124 @@ class ProjectManager {
|
||||
* Create a new project (select folder)
|
||||
*/
|
||||
async createNewProject() {
|
||||
// Trigger folder picker if available
|
||||
if (window.folderPicker && typeof window.folderPicker.pick === 'function') {
|
||||
try {
|
||||
const folder = await window.folderPicker.pick();
|
||||
if (folder) {
|
||||
await this.createSessionInFolder(folder);
|
||||
console.log('[ProjectManager] Creating new project...');
|
||||
|
||||
// Prompt user for project name
|
||||
const projectName = prompt('Enter project name:', 'My Project');
|
||||
if (!projectName || projectName.trim() === '') {
|
||||
console.log('[ProjectManager] Project creation cancelled');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// Create a new session with the project name
|
||||
// This will automatically create a new project if needed
|
||||
const workingDir = this.projects.size > 0 ?
|
||||
Array.from(this.projects.values())[0].workingDir :
|
||||
'/home/uroma/obsidian-vault';
|
||||
|
||||
// Create a unique project key from the project name
|
||||
const projectKey = projectName.trim().replace(/\s+/g, '-').toLowerCase();
|
||||
const newProjectId = `project-${projectKey}`;
|
||||
|
||||
// CRITICAL FIX: Give each manually created project a unique virtual workingDir
|
||||
// This prevents sessions from other projects leaking into this project
|
||||
const virtualWorkingDir = `/virtual/projects/${projectKey}`;
|
||||
|
||||
console.log('[ProjectManager] Creating project:', projectName, 'with key:', projectKey, 'and virtual workingDir:', virtualWorkingDir);
|
||||
|
||||
// Create the project in memory
|
||||
if (!this.projects.has(projectKey)) {
|
||||
this.projects.set(projectKey, {
|
||||
id: newProjectId,
|
||||
name: this.deduplicateProjectName(projectName, this.projects),
|
||||
workingDir: virtualWorkingDir, // Use unique virtual workingDir
|
||||
sessions: [],
|
||||
activeSessionId: null,
|
||||
createdAt: Date.now(),
|
||||
manuallyCreated: true, // Mark as manually created for persistence
|
||||
isVirtual: true // Flag to identify virtual projects
|
||||
});
|
||||
|
||||
// CRITICAL FIX: Save to localStorage
|
||||
this.saveManuallyCreatedProjects();
|
||||
|
||||
// Re-render project tabs
|
||||
this.renderProjectTabs();
|
||||
|
||||
// Switch to the new project
|
||||
await this.switchProject(newProjectId);
|
||||
|
||||
// Show success message
|
||||
if (typeof appendSystemMessage === 'function') {
|
||||
appendSystemMessage(`✅ Created project "${projectName}"`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[ProjectManager] Error picking folder:', error);
|
||||
this.showError('Failed to select folder');
|
||||
|
||||
console.log('[ProjectManager] Project created successfully:', newProjectId);
|
||||
} else {
|
||||
this.showError('Project already exists');
|
||||
}
|
||||
} else {
|
||||
// Fallback: prompt for folder or create default session
|
||||
await this.createNewSessionInProject('default');
|
||||
} catch (error) {
|
||||
console.error('[ProjectManager] Error creating project:', error);
|
||||
this.showError('Failed to create project: ' + error.message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new session in a specific folder
|
||||
* CRITICAL FIX: Added projectId parameter to associate sessions with their project
|
||||
*/
|
||||
async createSessionInFolder(workingDir) {
|
||||
async createSessionInFolder(workingDir, projectId = null, projectName = null) {
|
||||
try {
|
||||
if (typeof showLoadingOverlay === 'function') {
|
||||
showLoadingOverlay('Creating session...');
|
||||
}
|
||||
|
||||
// CRITICAL FIX: Add timeout to prevent hanging
|
||||
const controller = new AbortController();
|
||||
const timeoutId = setTimeout(() => controller.abort(), 10000); // 10 second timeout
|
||||
|
||||
// CRITICAL FIX: Include project metadata to properly associate session with project
|
||||
const sessionMetadata = {
|
||||
type: 'chat',
|
||||
source: 'web-ide'
|
||||
};
|
||||
|
||||
// Add project info to metadata if provided
|
||||
if (projectId && projectName) {
|
||||
sessionMetadata.projectId = projectId;
|
||||
sessionMetadata.project = projectName;
|
||||
console.log('[ProjectManager] Creating session in project:', projectName, 'with ID:', projectId);
|
||||
}
|
||||
|
||||
const res = await fetch('/claude/api/claude/sessions', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
signal: controller.signal,
|
||||
body: JSON.stringify({
|
||||
workingDir,
|
||||
metadata: {
|
||||
type: 'chat',
|
||||
source: 'web-ide'
|
||||
}
|
||||
metadata: sessionMetadata
|
||||
})
|
||||
});
|
||||
|
||||
if (!res.ok) throw new Error('Failed to create session');
|
||||
clearTimeout(timeoutId); // Clear timeout if request completes
|
||||
|
||||
if (!res.ok) {
|
||||
const errorText = await res.text();
|
||||
throw new Error(`HTTP ${res.status}: ${errorText}`);
|
||||
}
|
||||
|
||||
const data = await res.json();
|
||||
if (data.success) {
|
||||
if (data.success || data.id) {
|
||||
// Reload projects and switch to new session
|
||||
await this.loadProjects();
|
||||
await this.initialize();
|
||||
|
||||
// Find the new session and switch to it
|
||||
const session = data.session || data;
|
||||
for (const project of this.projects.values()) {
|
||||
const session = project.sessions.find(s => s.id === data.session.id);
|
||||
if (session) {
|
||||
const foundSession = project.sessions.find(s => s.id === session.id);
|
||||
if (foundSession) {
|
||||
this.switchProject(project.id);
|
||||
break;
|
||||
}
|
||||
@@ -354,18 +536,26 @@ class ProjectManager {
|
||||
if (typeof hideLoadingOverlay === 'function') {
|
||||
hideLoadingOverlay();
|
||||
}
|
||||
this.showError('Failed to create session');
|
||||
|
||||
// Special handling for timeout/abort errors
|
||||
if (error.name === 'AbortError') {
|
||||
this.showError('Request timed out. The server took too long to respond. Please try again.');
|
||||
} else {
|
||||
this.showError('Failed to create session: ' + error.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new session in the current project
|
||||
* CRITICAL FIX: Pass project info to ensure sessions stay in their project
|
||||
*/
|
||||
async createNewSessionInProject(projectId) {
|
||||
const project = this.projects.get(projectId.replace('project-', ''));
|
||||
if (!project) return;
|
||||
|
||||
await this.createSessionInFolder(project.workingDir);
|
||||
// CRITICAL FIX: Pass project ID and name to associate session with this project
|
||||
await this.createSessionInFolder(project.workingDir, project.id, project.name);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -406,6 +596,12 @@ class ProjectManager {
|
||||
// Re-render if this is the active project
|
||||
if (this.activeProjectId === project.id) {
|
||||
this.renderProjectTabs();
|
||||
|
||||
// CRITICAL FIX: Update left sidebar chat history with this project's sessions
|
||||
if (typeof loadChatHistory === 'function') {
|
||||
loadChatHistory(project.sessions);
|
||||
}
|
||||
|
||||
if (window.sessionTabs) {
|
||||
window.sessionTabs.setSessions(project.sessions);
|
||||
window.sessionTabs.render();
|
||||
|
||||
Reference in New Issue
Block a user