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

@@ -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();