diff --git a/public/claude-ide/projects.js b/public/claude-ide/projects.js
new file mode 100644
index 00000000..c7330292
--- /dev/null
+++ b/public/claude-ide/projects.js
@@ -0,0 +1,648 @@
+/**
+ * Projects Page JavaScript
+ * Handles project management, including CRUD operations, search, and recycle bin
+ */
+
+// === State Management ===
+let projects = [];
+let currentEditingProject = null;
+let currentContextMenuProjectId = null;
+
+// === DOM Elements ===
+const projectsGrid = document.getElementById('projects-grid');
+const emptyState = document.getElementById('empty-state');
+const searchInput = document.getElementById('search-input');
+const newProjectBtn = document.getElementById('new-project-btn');
+const emptyStateNewProjectBtn = document.getElementById('empty-state-new-project-btn');
+const recycleBinBtn = document.getElementById('recycle-bin-btn');
+const projectModal = document.getElementById('project-modal');
+const modalTitle = document.getElementById('modal-title');
+const projectForm = document.getElementById('project-form');
+const closeModalBtn = document.getElementById('close-modal-btn');
+const cancelModalBtn = document.getElementById('cancel-modal-btn');
+const recycleBinModal = document.getElementById('recycle-bin-modal');
+const closeRecycleBinBtn = document.getElementById('close-recycle-bin-btn');
+const recycleBinContent = document.getElementById('recycle-bin-content');
+const contextMenu = document.getElementById('context-menu');
+
+// === Initialization ===
+document.addEventListener('DOMContentLoaded', async () => {
+ await loadProjects();
+ setupEventListeners();
+});
+
+// === Event Listeners Setup ===
+function setupEventListeners() {
+ // Search functionality
+ searchInput.addEventListener('input', (e) => {
+ renderProjects(e.target.value);
+ });
+
+ // Create project buttons
+ newProjectBtn.addEventListener('click', () => openProjectModal());
+ emptyStateNewProjectBtn.addEventListener('click', () => openProjectModal());
+
+ // Modal close buttons
+ closeModalBtn.addEventListener('click', closeProjectModal);
+ cancelModalBtn.addEventListener('click', closeProjectModal);
+
+ // Project form submission
+ projectForm.addEventListener('submit', handleProjectSubmit);
+
+ // Recycle bin
+ recycleBinBtn.addEventListener('click', openRecycleBinModal);
+ closeRecycleBinBtn.addEventListener('click', () => {
+ recycleBinModal.style.display = 'none';
+ });
+
+ // Context menu actions
+ document.getElementById('ctx-open').addEventListener('click', () => {
+ if (currentContextMenuProjectId) {
+ openProject(currentContextMenuProjectId);
+ hideContextMenu();
+ }
+ });
+
+ document.getElementById('ctx-edit').addEventListener('click', () => {
+ if (currentContextMenuProjectId) {
+ const project = projects.find(p => p.id === currentContextMenuProjectId);
+ if (project) {
+ openProjectModal(project);
+ hideContextMenu();
+ }
+ }
+ });
+
+ document.getElementById('ctx-delete').addEventListener('click', () => {
+ if (currentContextMenuProjectId) {
+ deleteProject(currentContextMenuProjectId);
+ hideContextMenu();
+ }
+ });
+
+ // Close context menu on click outside
+ document.addEventListener('click', (e) => {
+ if (!contextMenu.contains(e.target)) {
+ hideContextMenu();
+ }
+ });
+
+ // Close modals on escape key
+ document.addEventListener('keydown', (e) => {
+ if (e.key === 'Escape') {
+ closeProjectModal();
+ recycleBinModal.style.display = 'none';
+ hideContextMenu();
+ }
+ });
+
+ // Close modals on backdrop click
+ projectModal.addEventListener('click', (e) => {
+ if (e.target === projectModal) {
+ closeProjectModal();
+ }
+ });
+
+ recycleBinModal.addEventListener('click', (e) => {
+ if (e.target === recycleBinModal) {
+ recycleBinModal.style.display = 'none';
+ }
+ });
+}
+
+// === API Functions ===
+
+/**
+ * Load projects from the server
+ */
+async function loadProjects() {
+ try {
+ projectsGrid.innerHTML = '
Loading projects...
';
+
+ const response = await fetch('/api/projects');
+ if (!response.ok) {
+ throw new Error(`HTTP error! status: ${response.status}`);
+ }
+
+ const data = await response.json();
+ projects = data.projects || [];
+
+ renderProjects();
+ } catch (error) {
+ console.error('Error loading projects:', error);
+ showToast('Failed to load projects', 'error');
+ projectsGrid.innerHTML = `
+
+
Error loading projects
+
${escapeHtml(error.message)}
+
+
+ `;
+ }
+}
+
+/**
+ * Create or update a project
+ */
+async function saveProject(projectData) {
+ try {
+ const url = currentEditingProject
+ ? `/api/projects/${currentEditingProject.id}`
+ : '/api/projects';
+
+ const method = currentEditingProject ? 'PUT' : 'POST';
+
+ const response = await fetch(url, {
+ method,
+ headers: {
+ 'Content-Type': 'application/json'
+ },
+ body: JSON.stringify(projectData)
+ });
+
+ if (!response.ok) {
+ const errorData = await response.json();
+ throw new Error(errorData.error || `HTTP error! status: ${response.status}`);
+ }
+
+ const result = await response.json();
+
+ if (currentEditingProject) {
+ // Update existing project in the array
+ const index = projects.findIndex(p => p.id === currentEditingProject.id);
+ if (index !== -1) {
+ projects[index] = result.project;
+ }
+ showToast('Project updated successfully', 'success');
+ } else {
+ // Add new project to the array
+ projects.push(result.project);
+ showToast('Project created successfully', 'success');
+ }
+
+ renderProjects(searchInput.value);
+ closeProjectModal();
+ } catch (error) {
+ console.error('Error saving project:', error);
+ showToast(error.message || 'Failed to save project', 'error');
+ throw error;
+ }
+}
+
+/**
+ * Soft delete a project
+ */
+async function deleteProject(projectId) {
+ if (!confirm('Are you sure you want to delete this project? It will be moved to the recycle bin.')) {
+ return;
+ }
+
+ try {
+ const response = await fetch(`/api/projects/${projectId}`, {
+ method: 'DELETE'
+ });
+
+ if (!response.ok) {
+ const errorData = await response.json();
+ throw new Error(errorData.error || `HTTP error! status: ${response.status}`);
+ }
+
+ // Remove project from array
+ projects = projects.filter(p => p.id !== projectId);
+ renderProjects(searchInput.value);
+ showToast('Project moved to recycle bin', 'success');
+ } catch (error) {
+ console.error('Error deleting project:', error);
+ showToast(error.message || 'Failed to delete project', 'error');
+ }
+}
+
+/**
+ * Load deleted projects for recycle bin
+ */
+async function loadDeletedProjects() {
+ try {
+ const response = await fetch('/api/projects?includeDeleted=true');
+ if (!response.ok) {
+ throw new Error(`HTTP error! status: ${response.status}`);
+ }
+
+ const data = await response.json();
+ return data.projects.filter(p => p.deletedAt) || [];
+ } catch (error) {
+ console.error('Error loading deleted projects:', error);
+ showToast('Failed to load recycle bin', 'error');
+ return [];
+ }
+}
+
+/**
+ * Restore a project from recycle bin
+ */
+async function restoreProject(projectId) {
+ try {
+ const response = await fetch(`/api/projects/${projectId}/restore`, {
+ method: 'POST'
+ });
+
+ if (!response.ok) {
+ const errorData = await response.json();
+ throw new Error(errorData.error || `HTTP error! status: ${response.status}`);
+ }
+
+ // Reload projects and recycle bin
+ await loadProjects();
+ await openRecycleBinModal();
+ showToast('Project restored successfully', 'success');
+ } catch (error) {
+ console.error('Error restoring project:', error);
+ showToast(error.message || 'Failed to restore project', 'error');
+ }
+}
+
+/**
+ * Permanently delete a project
+ */
+async function permanentDeleteProject(projectId) {
+ if (!confirm('Are you sure you want to permanently delete this project? This action cannot be undone.')) {
+ return;
+ }
+
+ try {
+ const response = await fetch(`/api/projects/${projectId}/permanent`, {
+ method: 'DELETE'
+ });
+
+ if (!response.ok) {
+ const errorData = await response.json();
+ throw new Error(errorData.error || `HTTP error! status: ${response.status}`);
+ }
+
+ // Reload recycle bin
+ await openRecycleBinModal();
+ showToast('Project permanently deleted', 'success');
+ } catch (error) {
+ console.error('Error permanently deleting project:', error);
+ showToast(error.message || 'Failed to delete project', 'error');
+ }
+}
+
+// === Render Functions ===
+
+/**
+ * Render project cards based on filter
+ */
+function renderProjects(filter = '') {
+ const filteredProjects = projects.filter(project => {
+ const searchTerm = filter.toLowerCase();
+ return (
+ project.name.toLowerCase().includes(searchTerm) ||
+ (project.description && project.description.toLowerCase().includes(searchTerm)) ||
+ project.path.toLowerCase().includes(searchTerm)
+ );
+ });
+
+ if (filteredProjects.length === 0) {
+ projectsGrid.style.display = 'none';
+ emptyState.style.display = 'block';
+ return;
+ }
+
+ projectsGrid.style.display = 'grid';
+ emptyState.style.display = 'none';
+
+ projectsGrid.innerHTML = filteredProjects.map(project => createProjectCard(project)).join('');
+}
+
+/**
+ * Create HTML for a project card
+ */
+function createProjectCard(project) {
+ const sessionsCount = project.sessionsCount || 0;
+ const lastActivity = project.lastActivity ? formatDate(project.lastActivity) : 'No activity';
+
+ return `
+
+
+ ${project.description ? `
${escapeHtml(project.description)}
` : ''}
+
${escapeHtml(project.path)}
+
+
+ Sessions:
+ ${sessionsCount}
+
+
+ Last activity:
+ ${lastActivity}
+
+
+
+ `;
+}
+
+/**
+ * Render recycle bin content
+ */
+async function openRecycleBinModal() {
+ recycleBinModal.style.display = 'flex';
+ recycleBinContent.innerHTML = 'Loading recycle bin...
';
+
+ const deletedProjects = await loadDeletedProjects();
+
+ if (deletedProjects.length === 0) {
+ recycleBinContent.innerHTML = `
+
+ `;
+ return;
+ }
+
+ recycleBinContent.innerHTML = deletedProjects.map(project => `
+
+
+
${escapeHtml(project.name)}
+
${escapeHtml(project.path)}
+
+
+
+
+
+
+ `).join('');
+
+ // Add event listeners for restore and delete buttons
+ recycleBinContent.querySelectorAll('.btn-restore').forEach(btn => {
+ btn.addEventListener('click', () => restoreProject(parseInt(btn.dataset.projectId)));
+ });
+
+ recycleBinContent.querySelectorAll('.btn-delete-permanent').forEach(btn => {
+ btn.addEventListener('click', () => permanentDeleteProject(parseInt(btn.dataset.projectId)));
+ });
+}
+
+// === Navigation Functions ===
+
+/**
+ * Navigate to sessions page for a project
+ */
+function openProject(projectId) {
+ const project = projects.find(p => p.id === projectId);
+ if (!project) {
+ showToast('Project not found', 'error');
+ return;
+ }
+
+ // Navigate to sessions page filtered by project
+ window.location.href = `/?projectId=${projectId}`;
+}
+
+// === Modal Functions ===
+
+/**
+ * Open project modal for creating or editing
+ */
+function openProjectModal(project = null) {
+ currentEditingProject = project;
+
+ if (project) {
+ modalTitle.textContent = 'Edit Project';
+ document.getElementById('project-name').value = project.name;
+ document.getElementById('project-path').value = project.path;
+ document.getElementById('project-description').value = project.description || '';
+ document.getElementById('project-icon').value = project.icon || '📁';
+ document.getElementById('project-color').value = project.color || '#4a9eff';
+ } else {
+ modalTitle.textContent = 'Create Project';
+ projectForm.reset();
+ document.getElementById('project-color').value = '#4a9eff';
+ }
+
+ projectModal.style.display = 'flex';
+ document.getElementById('project-name').focus();
+}
+
+/**
+ * Close project modal
+ */
+function closeProjectModal() {
+ projectModal.style.display = 'none';
+ currentEditingProject = null;
+ projectForm.reset();
+}
+
+/**
+ * Handle project form submission
+ */
+async function handleProjectSubmit(e) {
+ e.preventDefault();
+
+ const formData = new FormData(projectForm);
+ const projectData = {
+ name: formData.get('name').trim(),
+ path: formData.get('path').trim(),
+ description: formData.get('description').trim(),
+ icon: formData.get('icon').trim() || '📁',
+ color: formData.get('color') || '#4a9eff'
+ };
+
+ // Basic validation
+ if (!projectData.name) {
+ showToast('Project name is required', 'error');
+ return;
+ }
+
+ if (!projectData.path) {
+ showToast('Project path is required', 'error');
+ return;
+ }
+
+ try {
+ await saveProject(projectData);
+ } catch (error) {
+ // Error already handled in saveProject
+ }
+}
+
+// === Context Menu Functions ===
+
+/**
+ * Show context menu for a project
+ */
+function showProjectMenu(projectId, event) {
+ event.preventDefault();
+ event.stopPropagation();
+
+ currentContextMenuProjectId = projectId;
+
+ // Position the menu
+ const menuWidth = 160;
+ const menuHeight = 120;
+ const padding = 10;
+
+ let x = event.clientX;
+ let y = event.clientY;
+
+ // Prevent menu from going off screen
+ if (x + menuWidth > window.innerWidth - padding) {
+ x = window.innerWidth - menuWidth - padding;
+ }
+ if (y + menuHeight > window.innerHeight - padding) {
+ y = window.innerHeight - menuHeight - padding;
+ }
+
+ contextMenu.style.left = `${x}px`;
+ contextMenu.style.top = `${y}px`;
+ contextMenu.style.display = 'block';
+}
+
+/**
+ * Hide context menu
+ */
+function hideContextMenu() {
+ contextMenu.style.display = 'none';
+ currentContextMenuProjectId = null;
+}
+
+// === Utility Functions ===
+
+/**
+ * Escape HTML to prevent XSS attacks
+ */
+function escapeHtml(text) {
+ if (typeof text !== 'string') {
+ return '';
+ }
+
+ const div = document.createElement('div');
+ div.textContent = text;
+ return div.innerHTML;
+}
+
+/**
+ * Format date string to human-readable format
+ */
+function formatDate(dateString) {
+ if (!dateString) return 'Unknown';
+
+ const date = new Date(dateString);
+ const now = new Date();
+ const diffMs = now - date;
+ const diffMins = Math.floor(diffMs / 60000);
+ const diffHours = Math.floor(diffMs / 3600000);
+ const diffDays = Math.floor(diffMs / 86400000);
+
+ if (diffMins < 1) return 'Just now';
+ if (diffMins < 60) return `${diffMins}m ago`;
+ if (diffHours < 24) return `${diffHours}h ago`;
+ if (diffDays < 7) return `${diffDays}d ago`;
+
+ // For older dates, return formatted date
+ const options = { month: 'short', day: 'numeric', year: 'numeric' };
+ return date.toLocaleDateString('en-US', options);
+}
+
+/**
+ * Show toast notification
+ */
+function showToast(message, type = 'info') {
+ // Remove existing toast if any
+ const existingToast = document.querySelector('.toast');
+ if (existingToast) {
+ existingToast.remove();
+ }
+
+ // Create toast element
+ const toast = document.createElement('div');
+ toast.className = `toast toast-${type}`;
+ toast.textContent = message;
+
+ // Add styles
+ Object.assign(toast.style, {
+ position: 'fixed',
+ bottom: '2rem',
+ right: '2rem',
+ padding: '1rem 1.5rem',
+ borderRadius: '8px',
+ backgroundColor: type === 'success' ? '#10b981' : type === 'error' ? '#ef4444' : '#3b82f6',
+ color: 'white',
+ fontWeight: '500',
+ boxShadow: '0 4px 12px rgba(0, 0, 0, 0.3)',
+ zIndex: '10000',
+ animation: 'slideIn 0.3s ease',
+ maxWidth: '400px',
+ wordWrap: 'break-word'
+ });
+
+ // Add animation keyframes if not already present
+ if (!document.getElementById('toast-animations')) {
+ const style = document.createElement('style');
+ style.id = 'toast-animations';
+ style.textContent = `
+ @keyframes slideIn {
+ from {
+ transform: translateX(100%);
+ opacity: 0;
+ }
+ to {
+ transform: translateX(0);
+ opacity: 1;
+ }
+ }
+ @keyframes slideOut {
+ from {
+ transform: translateX(0);
+ opacity: 1;
+ }
+ to {
+ transform: translateX(100%);
+ opacity: 0;
+ }
+ }
+ `;
+ document.head.appendChild(style);
+ }
+
+ document.body.appendChild(toast);
+
+ // Auto remove after 3 seconds
+ setTimeout(() => {
+ toast.style.animation = 'slideOut 0.3s ease';
+ setTimeout(() => toast.remove(), 300);
+ }, 3000);
+}
+
+// === Event Delegation for Dynamic Elements ===
+projectsGrid.addEventListener('click', (e) => {
+ const projectCard = e.target.closest('.project-card');
+ const menuBtn = e.target.closest('.project-menu-btn');
+
+ if (menuBtn) {
+ e.stopPropagation();
+ const projectId = parseInt(menuBtn.dataset.projectId);
+ showProjectMenu(projectId, e);
+ } else if (projectCard) {
+ const projectId = parseInt(projectCard.dataset.projectId);
+ openProject(projectId);
+ }
+});
+
+// Make functions globally available for HTML event handlers
+window.loadProjects = loadProjects;
+window.openProjectModal = openProjectModal;
+window.closeProjectModal = closeProjectModal;
+window.openRecycleBinModal = openRecycleBinModal;
+window.openProject = openProject;
+window.deleteProject = deleteProject;
+window.restoreProject = restoreProject;
+window.permanentDeleteProject = permanentDeleteProject;