diff --git a/database.sqlite b/database.sqlite
new file mode 100644
index 00000000..8b63d091
Binary files /dev/null and b/database.sqlite differ
diff --git a/database.sqlite-shm b/database.sqlite-shm
new file mode 100644
index 00000000..f8c74514
Binary files /dev/null and b/database.sqlite-shm differ
diff --git a/database.sqlite-wal b/database.sqlite-wal
new file mode 100644
index 00000000..c8f2aa4f
Binary files /dev/null and b/database.sqlite-wal differ
diff --git a/docs/plans/2025-01-19-project-session-organization.md b/docs/plans/2025-01-19-project-session-organization.md
new file mode 100644
index 00000000..dfaee3c1
--- /dev/null
+++ b/docs/plans/2025-01-19-project-session-organization.md
@@ -0,0 +1,2232 @@
+# Project and Session Organization Implementation Plan
+
+> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
+
+**Goal:** Implement persistent projects as first-class entities containing multiple sessions, with intelligent auto-assignment, manual reassignment via context menu, and soft-delete recycle bin.
+
+**Architecture:** Introduce a `projects` collection in MongoDB that stores project metadata (name, description, icon, color, path, session IDs). Sessions reference projects via `projectId`. Soft delete implemented with `deletedAt` timestamp. API endpoints provide CRUD operations, smart suggestions based on directory/recency/name matching. Frontend adds dedicated projects page, context menu for session reassignment, and recycle bin interface.
+
+**Tech Stack:** MongoDB (database), Express.js (API), Vanilla JavaScript (frontend), CSS (styling), xterm.js (existing terminal)
+
+---
+
+## Task 1: Database Setup - Create Projects Collection
+
+**Files:**
+- Modify: `server.js:39-45` (after claudeService initialization)
+
+**Step 1: Add projects collection initialization**
+
+Find the section where collections are created (around line 39-45). Add projects collection initialization.
+
+```javascript
+// Initialize collections
+const db = client.db();
+const sessionsCollection = db.collection('sessions');
+const projectsCollection = db.collection('projects'); // ADD THIS LINE
+
+// Create indexes for better query performance
+await projectsCollection.createIndex({ deletedAt: 1 });
+await projectsCollection.createIndex({ name: 1 });
+```
+
+**Step 2: Commit**
+
+```bash
+git add server.js
+git commit -m "feat: add projects collection initialization and indexes"
+```
+
+---
+
+## Task 2: Create Project API Endpoints
+
+**Files:**
+- Modify: `server.js` (add after sessions routes, around line 900)
+
+**Step 1: Add GET /api/projects endpoint**
+
+```javascript
+// GET /api/projects - List all active projects
+app.get('/api/projects', requireAuth, async (req, res) => {
+ try {
+ const projects = await projectsCollection.find({
+ deletedAt: null
+ }).sort({ lastActivity: -1 }).toArray();
+
+ res.json({
+ success: true,
+ projects: projects.map(p => ({
+ id: p._id,
+ name: p.name,
+ description: p.description,
+ icon: p.icon,
+ color: p.color,
+ path: p.path,
+ sessionCount: p.sessionIds?.length || 0,
+ createdAt: p.createdAt,
+ lastActivity: p.lastActivity
+ }))
+ });
+ } catch (error) {
+ console.error('Error fetching projects:', error);
+ res.status(500).json({ error: 'Failed to fetch projects' });
+ }
+});
+```
+
+**Step 2: Add POST /api/projects endpoint**
+
+```javascript
+// POST /api/projects - Create new project
+app.post('/api/projects', requireAuth, async (req, res) => {
+ try {
+ const { name, path, description, icon, color } = req.body;
+
+ // Validate required fields
+ if (!name || !path) {
+ return res.status(400).json({ error: 'Name and path are required' });
+ }
+
+ // Check if project with same name already exists
+ const existing = await projectsCollection.findOne({
+ name,
+ deletedAt: null
+ });
+
+ if (existing) {
+ return res.status(409).json({ error: 'Project with this name already exists' });
+ }
+
+ const project = {
+ name,
+ path,
+ description: description || '',
+ icon: icon || 'š',
+ color: color || '#4a9eff',
+ sessionIds: [],
+ createdAt: new Date(),
+ lastActivity: new Date(),
+ deletedAt: null
+ };
+
+ const result = await projectsCollection.insertOne(project);
+
+ res.status(201).json({
+ success: true,
+ project: {
+ id: result.insertedId,
+ ...project
+ }
+ });
+ } catch (error) {
+ console.error('Error creating project:', error);
+ res.status(500).json({ error: 'Failed to create project' });
+ }
+});
+```
+
+**Step 3: Add PUT /api/projects/:id endpoint**
+
+```javascript
+// PUT /api/projects/:id - Update project
+app.put('/api/projects/:id', requireAuth, async (req, res) => {
+ try {
+ const { id } = req.params;
+ const { name, description, icon, color, path } = req.body;
+
+ const update = {};
+ if (name) update.name = name;
+ if (description !== undefined) update.description = description;
+ if (icon) update.icon = icon;
+ if (color) update.color = color;
+ if (path) update.path = path;
+
+ const result = await projectsCollection.findOneAndUpdate(
+ { _id: new ObjectId(id), deletedAt: null },
+ { $set: update },
+ { returnDocument: 'after' }
+ );
+
+ if (!result) {
+ return res.status(404).json({ error: 'Project not found' });
+ }
+
+ res.json({
+ success: true,
+ project: {
+ id: result._id,
+ name: result.name,
+ description: result.description,
+ icon: result.icon,
+ color: result.color,
+ path: result.path,
+ sessionCount: result.sessionIds?.length || 0,
+ createdAt: result.createdAt,
+ lastActivity: result.lastActivity
+ }
+ });
+ } catch (error) {
+ console.error('Error updating project:', error);
+ res.status(500).json({ error: 'Failed to update project' });
+ }
+});
+```
+
+**Step 4: Commit**
+
+```bash
+git add server.js
+git commit -m "feat: add project CRUD API endpoints"
+```
+
+---
+
+## Task 3: Soft Delete and Recycle Bin Endpoints
+
+**Files:**
+- Modify: `server.js` (after project CRUD endpoints)
+
+**Step 1: Add DELETE /api/projects/:id (soft delete)**
+
+```javascript
+// DELETE /api/projects/:id - Soft delete project
+app.delete('/api/projects/:id', requireAuth, async (req, res) => {
+ try {
+ const { id } = req.params;
+
+ // Get project to soft delete its sessions too
+ const project = await projectsCollection.findOne({
+ _id: new ObjectId(id),
+ deletedAt: null
+ });
+
+ if (!project) {
+ return res.status(404).json({ error: 'Project not found' });
+ }
+
+ // Soft delete project
+ await projectsCollection.updateOne(
+ { _id: new ObjectId(id) },
+ { $set: { deletedAt: new Date() } }
+ );
+
+ // Soft delete all sessions in this project
+ await sessionsCollection.updateMany(
+ { projectId: new ObjectId(id) },
+ { $set: { deletedAt: new Date() } }
+ );
+
+ res.json({ success: true });
+ } catch (error) {
+ console.error('Error deleting project:', error);
+ res.status(500).json({ error: 'Failed to delete project' });
+ }
+});
+```
+
+**Step 2: Add POST /api/projects/:id/restore**
+
+```javascript
+// POST /api/projects/:id/restore - Restore from recycle bin
+app.post('/api/projects/:id/restore', requireAuth, async (req, res) => {
+ try {
+ const { id } = req.params;
+
+ // Restore project
+ const project = await projectsCollection.findOneAndUpdate(
+ { _id: new ObjectId(id), deletedAt: { $ne: null } },
+ { $set: { deletedAt: null } },
+ { returnDocument: 'after' }
+ );
+
+ if (!project) {
+ return res.status(404).json({ error: 'Project not found in recycle bin' });
+ }
+
+ // Restore all sessions in this project
+ await sessionsCollection.updateMany(
+ { projectId: new ObjectId(id) },
+ { $set: { deletedAt: null } }
+ );
+
+ res.json({ success: true });
+ } catch (error) {
+ console.error('Error restoring project:', error);
+ res.status(500).json({ error: 'Failed to restore project' });
+ }
+});
+```
+
+**Step 3: Add DELETE /api/projects/:id/permanent**
+
+```javascript
+// DELETE /api/projects/:id/permanent - Permanent delete
+app.delete('/api/projects/:id/permanent', requireAuth, async (req, res) => {
+ try {
+ const { id } = req.params;
+
+ // Permanently delete sessions
+ await sessionsCollection.deleteMany({
+ projectId: new ObjectId(id)
+ });
+
+ // Permanently delete project
+ await projectsCollection.deleteOne({
+ _id: new ObjectId(id)
+ });
+
+ res.json({ success: true });
+ } catch (error) {
+ console.error('Error permanently deleting project:', error);
+ res.status(500).json({ error: 'Failed to permanently delete project' });
+ }
+});
+```
+
+**Step 4: Add GET /api/recycle-bin**
+
+```javascript
+// GET /api/recycle-bin - List deleted items
+app.get('/api/recycle-bin', requireAuth, async (req, res) => {
+ try {
+ const deletedProjects = await projectsCollection.find({
+ deletedAt: { $ne: null }
+ }).sort({ deletedAt: -1 }).toArray();
+
+ const result = await Promise.all(deletedProjects.map(async (project) => {
+ // Get session count for this project
+ const sessionCount = await sessionsCollection.countDocuments({
+ projectId: project._id,
+ deletedAt: { $ne: null }
+ });
+
+ return {
+ id: project._id,
+ name: project.name,
+ description: project.description,
+ icon: project.icon,
+ path: project.path,
+ sessionCount,
+ deletedAt: project.deletedAt
+ };
+ }));
+
+ res.json({
+ success: true,
+ items: result
+ });
+ } catch (error) {
+ console.error('Error fetching recycle bin:', error);
+ res.status(500).json({ error: 'Failed to fetch recycle bin' });
+ }
+});
+```
+
+**Step 5: Commit**
+
+```bash
+git add server.js
+git commit -m "feat: add soft delete, restore, permanent delete, and recycle bin endpoints"
+```
+
+---
+
+## Task 4: Session Reassignment Endpoint
+
+**Files:**
+- Modify: `server.js` (after recycle bin endpoint)
+
+**Step 1: Add POST /api/sessions/:id/move endpoint**
+
+```javascript
+// POST /api/sessions/:id/move - Move session to different project
+app.post('/api/sessions/:id/move', requireAuth, async (req, res) => {
+ try {
+ const { id } = req.params;
+ const { projectId } = req.body; // null for unassigned
+
+ // Verify session exists
+ const session = await sessionsCollection.findOne({
+ _id: new ObjectId(id),
+ deletedAt: null
+ });
+
+ if (!session) {
+ return res.status(404).json({ error: 'Session not found' });
+ }
+
+ // If moving to a project, verify it exists and is not deleted
+ if (projectId) {
+ const project = await projectsCollection.findOne({
+ _id: new ObjectId(projectId),
+ deletedAt: null
+ });
+
+ if (!project) {
+ return res.status(404).json({ error: 'Project not found' });
+ }
+
+ // Remove session from old project's sessionIds
+ if (session.projectId) {
+ await projectsCollection.updateOne(
+ { _id: session.projectId },
+ { $pull: { sessionIds: new ObjectId(id) } }
+ );
+ }
+
+ // Add session to new project's sessionIds
+ await projectsCollection.updateOne(
+ { _id: new ObjectId(projectId) },
+ { $push: { sessionIds: new ObjectId(id) } }
+ );
+ } else {
+ // Moving to unassigned - remove from old project
+ if (session.projectId) {
+ await projectsCollection.updateOne(
+ { _id: session.projectId },
+ { $pull: { sessionIds: new ObjectId(id) } }
+ );
+ }
+ }
+
+ // Update session's projectId
+ await sessionsCollection.updateOne(
+ { _id: new ObjectId(id) },
+ { $set: { projectId: projectId ? new ObjectId(projectId) : null } }
+ );
+
+ res.json({ success: true });
+ } catch (error) {
+ console.error('Error moving session:', error);
+ res.status(500).json({ error: 'Failed to move session' });
+ }
+});
+```
+
+**Step 2: Commit**
+
+```bash
+git add server.js
+git commit -m "feat: add session move endpoint"
+```
+
+---
+
+## Task 5: Smart Suggestions Endpoint
+
+**Files:**
+- Modify: `server.js` (after session move endpoint)
+
+**Step 1: Add GET /api/projects/suggestions endpoint**
+
+```javascript
+// GET /api/projects/suggestions?sessionId=xxx - Get project suggestions for a session
+app.get('/api/projects/suggestions', requireAuth, async (req, res) => {
+ try {
+ const { sessionId } = req.query;
+
+ if (!sessionId) {
+ return res.status(400).json({ error: 'sessionId is required' });
+ }
+
+ // Get session
+ const session = await sessionsCollection.findOne({
+ _id: new ObjectId(sessionId),
+ deletedAt: null
+ });
+
+ if (!session) {
+ return res.status(404).json({ error: 'Session not found' });
+ }
+
+ // Get all active projects
+ const projects = await projectsCollection.find({
+ deletedAt: null
+ }).toArray();
+
+ // Calculate suggestions
+ const suggestions = [];
+
+ for (const project of projects) {
+ let score = 0;
+ const reasons = [];
+
+ // Directory matching (high weight)
+ if (session.workingDir === project.path) {
+ score += 90;
+ reasons.push('Same directory');
+ } else if (session.workingDir?.startsWith(project.path)) {
+ score += 50;
+ reasons.push('Subdirectory');
+ }
+
+ // Recency (medium weight)
+ const daysSinceActivity = (Date.now() - project.lastActivity) / (1000 * 60 * 60 * 24);
+ if (daysSinceActivity < 1) {
+ score += 20;
+ reasons.push('Used today');
+ } else if (daysSinceActivity < 7) {
+ score += 10;
+ reasons.push(`Used ${Math.floor(daysSinceActivity)} days ago`);
+ }
+
+ // Name similarity (low weight)
+ if (session.name?.includes(project.name) || project.name.includes(session.name)) {
+ score += 15;
+ reasons.push('Similar name');
+ }
+
+ if (score > 0) {
+ suggestions.push({
+ id: project._id,
+ name: project.name,
+ icon: project.icon,
+ color: project.color,
+ score,
+ reasons
+ });
+ }
+ }
+
+ // Sort by score and take top 3
+ const topSuggestions = suggestions
+ .sort((a, b) => b.score - a.score)
+ .slice(0, 3);
+
+ // Get all projects for "show all" option
+ const allProjects = projects.map(p => ({
+ id: p._id,
+ name: p.name,
+ icon: p.icon,
+ color: p.color
+ })).sort((a, b) => a.name.localeCompare(b.name));
+
+ res.json({
+ success: true,
+ suggestions: topSuggestions,
+ allProjects
+ });
+ } catch (error) {
+ console.error('Error getting suggestions:', error);
+ res.status(500).json({ error: 'Failed to get suggestions' });
+ }
+});
+```
+
+**Step 2: Commit**
+
+```bash
+git add server.js
+git commit -m "feat: add smart project suggestions endpoint"
+```
+
+---
+
+## Task 6: Projects Page HTML
+
+**Files:**
+- Create: `public/projects.html`
+
+**Step 1: Create projects page structure**
+
+```html
+
+
+
+
+
+ Projects - Claude Code Web Interface
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
š
+
No projects yet
+
Create your first project to organize your sessions
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+**Step 2: Commit**
+
+```bash
+git add public/projects.html
+git commit -m "feat: add projects page HTML structure"
+```
+
+---
+
+## Task 7: Projects Page CSS
+
+**Files:**
+- Create: `public/claude-ide/projects.css`
+
+**Step 1: Create projects page styles**
+
+```css
+/* Projects Page Layout */
+.projects-page {
+ background: var(--bg-primary);
+ min-height: 100vh;
+}
+
+.projects-container {
+ max-width: 1400px;
+ margin: 0 auto;
+ padding: 2rem;
+}
+
+/* Header */
+.projects-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 2rem;
+ padding-bottom: 1rem;
+ border-bottom: 1px solid var(--border-color);
+}
+
+.header-left {
+ display: flex;
+ align-items: center;
+ gap: 1rem;
+}
+
+.header-left h1 {
+ margin: 0;
+ font-size: 2rem;
+}
+
+.back-link {
+ color: var(--text-secondary);
+ text-decoration: none;
+ transition: color 0.2s;
+}
+
+.back-link:hover {
+ color: var(--accent-color);
+}
+
+.header-right {
+ display: flex;
+ gap: 0.75rem;
+ align-items: center;
+}
+
+.search-box input {
+ padding: 0.5rem 1rem;
+ border: 1px solid var(--border-color);
+ border-radius: 6px;
+ background: var(--bg-secondary);
+ color: var(--text-primary);
+ width: 250px;
+}
+
+/* Projects Grid */
+.projects-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
+ gap: 1.5rem;
+}
+
+/* Project Card */
+.project-card {
+ background: var(--bg-secondary);
+ border: 1px solid var(--border-color);
+ border-radius: 12px;
+ padding: 1.5rem;
+ cursor: pointer;
+ transition: all 0.2s;
+ position: relative;
+}
+
+.project-card:hover {
+ transform: translateY(-2px);
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
+ border-color: var(--accent-color);
+}
+
+.project-card-header {
+ display: flex;
+ align-items: flex-start;
+ gap: 1rem;
+ margin-bottom: 1rem;
+}
+
+.project-icon {
+ font-size: 2rem;
+ line-height: 1;
+}
+
+.project-info {
+ flex: 1;
+}
+
+.project-name {
+ font-size: 1.25rem;
+ font-weight: 600;
+ margin: 0 0 0.25rem 0;
+ color: var(--text-primary);
+}
+
+.project-description {
+ font-size: 0.875rem;
+ color: var(--text-secondary);
+ margin: 0;
+ line-height: 1.4;
+}
+
+.project-menu-btn {
+ background: none;
+ border: none;
+ color: var(--text-secondary);
+ cursor: pointer;
+ padding: 0.25rem;
+ opacity: 0;
+ transition: opacity 0.2s;
+}
+
+.project-card:hover .project-menu-btn {
+ opacity: 1;
+}
+
+.project-menu-btn:hover {
+ color: var(--text-primary);
+}
+
+.project-meta {
+ margin-top: 1rem;
+ padding-top: 1rem;
+ border-top: 1px solid var(--border-color);
+}
+
+.project-path {
+ font-family: 'Monaco', 'Menlo', monospace;
+ font-size: 0.75rem;
+ color: var(--text-secondary);
+ margin-bottom: 0.5rem;
+ word-break: break-all;
+}
+
+.project-stats {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ font-size: 0.875rem;
+ color: var(--text-secondary);
+}
+
+.session-count {
+ display: flex;
+ align-items: center;
+ gap: 0.25rem;
+}
+
+.last-activity {
+ font-size: 0.75rem;
+}
+
+/* Empty State */
+.empty-state {
+ text-align: center;
+ padding: 4rem 2rem;
+}
+
+.empty-icon {
+ font-size: 4rem;
+ margin-bottom: 1rem;
+ opacity: 0.5;
+}
+
+.empty-state h2 {
+ margin-bottom: 0.5rem;
+}
+
+.empty-state p {
+ color: var(--text-secondary);
+ margin-bottom: 1.5rem;
+}
+
+/* Modal */
+.modal {
+ position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background: rgba(0, 0, 0, 0.5);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ z-index: 1000;
+}
+
+.modal-content {
+ background: var(--bg-primary);
+ border-radius: 12px;
+ padding: 2rem;
+ max-width: 500px;
+ width: 90%;
+ max-height: 90vh;
+ overflow-y: auto;
+}
+
+.modal-large {
+ max-width: 700px;
+}
+
+.modal-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 1.5rem;
+}
+
+.modal-header h2 {
+ margin: 0;
+}
+
+.modal-close {
+ background: none;
+ border: none;
+ font-size: 1.5rem;
+ cursor: pointer;
+ color: var(--text-secondary);
+}
+
+.modal-close:hover {
+ color: var(--text-primary);
+}
+
+/* Form */
+.form-group {
+ margin-bottom: 1rem;
+}
+
+.form-group label {
+ display: block;
+ margin-bottom: 0.5rem;
+ font-weight: 500;
+ color: var(--text-primary);
+}
+
+.form-group input,
+.form-group textarea {
+ width: 100%;
+ padding: 0.75rem;
+ border: 1px solid var(--border-color);
+ border-radius: 6px;
+ background: var(--bg-secondary);
+ color: var(--text-primary);
+ font-size: 1rem;
+}
+
+.form-row {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ gap: 1rem;
+}
+
+.form-actions {
+ display: flex;
+ justify-content: flex-end;
+ gap: 0.75rem;
+ margin-top: 1.5rem;
+}
+
+/* Buttons */
+.btn {
+ padding: 0.5rem 1rem;
+ border: none;
+ border-radius: 6px;
+ font-size: 0.875rem;
+ font-weight: 500;
+ cursor: pointer;
+ transition: all 0.2s;
+ display: inline-flex;
+ align-items: center;
+ gap: 0.5rem;
+}
+
+.btn-primary {
+ background: var(--accent-color);
+ color: white;
+}
+
+.btn-primary:hover {
+ opacity: 0.9;
+}
+
+.btn-secondary {
+ background: var(--bg-secondary);
+ color: var(--text-primary);
+ border: 1px solid var(--border-color);
+}
+
+.btn-secondary:hover {
+ background: var(--border-color);
+}
+
+/* Context Menu */
+.context-menu {
+ position: fixed;
+ background: var(--bg-primary);
+ border: 1px solid var(--border-color);
+ border-radius: 8px;
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
+ min-width: 200px;
+ z-index: 1001;
+ padding: 0.25rem 0;
+}
+
+.context-menu-item {
+ padding: 0.5rem 1rem;
+ cursor: pointer;
+ display: flex;
+ align-items: center;
+ gap: 0.5rem;
+ font-size: 0.875rem;
+}
+
+.context-menu-item:hover {
+ background: var(--bg-secondary);
+}
+
+.context-menu-divider {
+ height: 1px;
+ background: var(--border-color);
+ margin: 0.25rem 0;
+}
+
+/* Recycle Bin */
+.recycle-bin-items {
+ display: flex;
+ flex-direction: column;
+ gap: 1rem;
+}
+
+.recycle-bin-item {
+ background: var(--bg-secondary);
+ border: 1px solid var(--border-color);
+ border-radius: 8px;
+ padding: 1rem;
+ opacity: 0.7;
+}
+
+.recycle-bin-item-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 0.5rem;
+}
+
+.recycle-bin-actions {
+ display: flex;
+ gap: 0.5rem;
+}
+
+/* Buttons */
+.btn-sm {
+ padding: 0.25rem 0.5rem;
+ font-size: 0.75rem;
+}
+
+.btn-success {
+ background: #28a745;
+ color: white;
+}
+
+.btn-danger {
+ background: #dc3545;
+ color: white;
+}
+```
+
+**Step 2: Commit**
+
+```bash
+git add public/claude-ide/projects.css
+git commit -m "feat: add projects page styles"
+```
+
+---
+
+## Task 8: Projects Page JavaScript
+
+**Files:**
+- Create: `public/claude-ide/projects.js`
+
+**Step 1: Create projects page logic**
+
+```javascript
+// State
+let projects = [];
+let currentEditingProject = null;
+
+// Initialize
+document.addEventListener('DOMContentLoaded', () => {
+ loadProjects();
+ setupEventListeners();
+});
+
+// Load projects
+async function loadProjects() {
+ try {
+ const response = await fetch('/api/projects');
+ const data = await response.json();
+
+ if (data.success) {
+ projects = data.projects;
+ renderProjects();
+ }
+ } catch (error) {
+ console.error('Error loading projects:', error);
+ showToast('Failed to load projects', 'error');
+ }
+}
+
+// Render projects
+function renderProjects(filter = '') {
+ const grid = document.getElementById('projectsGrid');
+ const emptyState = document.getElementById('emptyState');
+
+ const filteredProjects = projects.filter(p =>
+ p.name.toLowerCase().includes(filter.toLowerCase()) ||
+ p.description.toLowerCase().includes(filter.toLowerCase()) ||
+ p.path.toLowerCase().includes(filter.toLowerCase())
+ );
+
+ if (filteredProjects.length === 0) {
+ grid.style.display = 'none';
+ emptyState.style.display = 'block';
+ return;
+ }
+
+ grid.style.display = 'grid';
+ emptyState.style.display = 'none';
+
+ grid.innerHTML = filteredProjects.map(project => `
+
+ `).join('');
+}
+
+// Setup event listeners
+function setupEventListeners() {
+ // Search
+ document.getElementById('projectSearch').addEventListener('input', (e) => {
+ renderProjects(e.target.value);
+ });
+
+ // Create project
+ document.getElementById('createProjectBtn').addEventListener('click', () => {
+ openProjectModal();
+ });
+
+ // Project form
+ document.getElementById('projectForm').addEventListener('submit', handleProjectSubmit);
+
+ // Recycle bin
+ document.getElementById('recycleBinBtn').addEventListener('click', openRecycleBinModal);
+
+ // Close context menu on click outside
+ document.addEventListener('click', () => {
+ hideContextMenu();
+ });
+}
+
+// Open project
+function openProject(projectId) {
+ // Navigate to sessions landing with project filter
+ window.location.href = `/claude/?project=${projectId}`;
+}
+
+// Open project modal (create or edit)
+function openProjectModal(project = null) {
+ currentEditingProject = project;
+
+ const modal = document.getElementById('projectModal');
+ const title = document.getElementById('modalTitle');
+ const form = document.getElementById('projectForm');
+
+ if (project) {
+ title.textContent = 'Edit Project';
+ document.getElementById('projectName').value = project.name;
+ document.getElementById('projectPath').value = project.path;
+ document.getElementById('projectDescription').value = project.description || '';
+ document.getElementById('projectIcon').value = project.icon || 'š';
+ document.getElementById('projectColor').value = project.color || '#4a9eff';
+ } else {
+ title.textContent = 'Create New Project';
+ form.reset();
+ document.getElementById('projectIcon').value = 'š';
+ document.getElementById('projectColor').value = '#4a9eff';
+ }
+
+ modal.style.display = 'flex';
+}
+
+// Close project modal
+function closeProjectModal() {
+ document.getElementById('projectModal').style.display = 'none';
+ currentEditingProject = null;
+}
+
+// Handle project submit
+async function handleProjectSubmit(e) {
+ e.preventDefault();
+
+ const data = {
+ name: document.getElementById('projectName').value,
+ path: document.getElementById('projectPath').value,
+ description: document.getElementById('projectDescription').value,
+ icon: document.getElementById('projectIcon').value || 'š',
+ color: document.getElementById('projectColor').value || '#4a9eff'
+ };
+
+ 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(data)
+ });
+
+ const result = await response.json();
+
+ if (result.success) {
+ showToast(currentEditingProject ? 'Project updated' : 'Project created', 'success');
+ closeProjectModal();
+ await loadProjects();
+ } else {
+ showToast(result.error || 'Failed to save project', 'error');
+ }
+ } catch (error) {
+ console.error('Error saving project:', error);
+ showToast('Failed to save project', 'error');
+ }
+}
+
+// Show project context menu
+function showProjectMenu(projectId, event) {
+ const project = projects.find(p => p.id === projectId);
+ if (!project) return;
+
+ const menu = document.getElementById('contextMenu');
+ menu.innerHTML = `
+
+
+ `;
+
+ menu.style.left = event.pageX + 'px';
+ menu.style.top = event.pageY + 'px';
+ menu.style.display = 'block';
+}
+
+// Hide context menu
+function hideContextMenu() {
+ document.getElementById('contextMenu').style.display = 'none';
+}
+
+// Open project modal by ID
+function openProjectModalById(projectId) {
+ const project = projects.find(p => p.id === projectId);
+ if (project) {
+ openProjectModal(project);
+ }
+ hideContextMenu();
+}
+
+// Delete project
+async function deleteProject(projectId) {
+ if (!confirm('Move this project and all its sessions to the recycle bin?')) {
+ return;
+ }
+
+ try {
+ const response = await fetch(`/api/projects/${projectId}`, {
+ method: 'DELETE'
+ });
+
+ const result = await response.json();
+
+ if (result.success) {
+ showToast('Project moved to recycle bin', 'success');
+ await loadProjects();
+ } else {
+ showToast(result.error || 'Failed to delete project', 'error');
+ }
+ } catch (error) {
+ console.error('Error deleting project:', error);
+ showToast('Failed to delete project', 'error');
+ }
+
+ hideContextMenu();
+}
+
+// Recycle bin modal
+async function openRecycleBinModal() {
+ const modal = document.getElementById('recycleBinModal');
+ const container = document.getElementById('recycleBinItems');
+
+ try {
+ const response = await fetch('/api/recycle-bin');
+ const data = await response.json();
+
+ if (data.success) {
+ if (data.items.length === 0) {
+ container.innerHTML = 'Recycle bin is empty š
';
+ } else {
+ container.innerHTML = data.items.map(item => `
+
+
+
+ `).join('');
+ }
+
+ modal.style.display = 'flex';
+ }
+ } catch (error) {
+ console.error('Error loading recycle bin:', error);
+ showToast('Failed to load recycle bin', 'error');
+ }
+}
+
+// Close recycle bin modal
+function closeRecycleBinModal() {
+ document.getElementById('recycleBinModal').style.display = 'none';
+}
+
+// Restore project
+async function restoreProject(projectId) {
+ try {
+ const response = await fetch(`/api/projects/${projectId}/restore`, {
+ method: 'POST'
+ });
+
+ const result = await response.json();
+
+ if (result.success) {
+ showToast('Project restored', 'success');
+ await loadProjects();
+ await openRecycleBinModal(); // Refresh bin
+ } else {
+ showToast(result.error || 'Failed to restore project', 'error');
+ }
+ } catch (error) {
+ console.error('Error restoring project:', error);
+ showToast('Failed to restore project', 'error');
+ }
+}
+
+// Permanent delete
+async function permanentDeleteProject(projectId) {
+ if (!confirm('Permanently delete this project and all its sessions? This cannot be undone!')) {
+ return;
+ }
+
+ try {
+ const response = await fetch(`/api/projects/${projectId}/permanent`, {
+ method: 'DELETE'
+ });
+
+ const result = await response.json();
+
+ if (result.success) {
+ showToast('Project permanently deleted', 'success');
+ await openRecycleBinModal(); // Refresh bin
+ } else {
+ showToast(result.error || 'Failed to delete project', 'error');
+ }
+ } catch (error) {
+ console.error('Error deleting project:', error);
+ showToast('Failed to delete project', 'error');
+ }
+}
+
+// Helper functions
+function escapeHtml(text) {
+ const div = document.createElement('div');
+ div.textContent = text;
+ return div.innerHTML;
+}
+
+function formatDate(dateString) {
+ 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`;
+ return date.toLocaleDateString();
+}
+
+function showToast(message, type = 'info') {
+ // Import from ide.js or implement locally
+ if (typeof window.showToast === 'function') {
+ window.showToast(message, type);
+ } else {
+ alert(message);
+ }
+}
+```
+
+**Step 2: Commit**
+
+```bash
+git add public/claude-ide/projects.js
+git commit -m "feat: add projects page JavaScript functionality"
+```
+
+---
+
+## Task 9: Update Sessions Landing to Show Projects
+
+**Files:**
+- Modify: `public/claude-ide/sessions-landing.js`
+
+**Step 1: Modify renderSessions to group by project**
+
+Find the `renderSessions` function and modify it to group sessions by project. This will require fetching projects first, then organizing sessions.
+
+Add this after the sessions are fetched:
+
+```javascript
+// Add project fetching
+async function loadSessionsAndProjects() {
+ const [sessionsRes, projectsRes] = await Promise.all([
+ fetch('/claude/api/claude/sessions'),
+ fetch('/api/projects')
+ ]);
+
+ const sessionsData = await sessionsRes.json();
+ const projectsData = await projectsRes.json();
+
+ if (sessionsData.success) {
+ sessions = sessionsData.sessions;
+ }
+
+ if (projectsData.success) {
+ const projects = projectsData.projects;
+ // Create a map for quick lookup
+ window.projectsMap = new Map(projects.map(p => [p.id.toString(), p]));
+ }
+
+ renderSessions();
+}
+```
+
+Modify `renderSessions` to group by project:
+
+```javascript
+function renderSessions() {
+ const container = document.getElementById('sessionsGrid');
+
+ // Group sessions by project
+ const grouped = {
+ unassigned: [],
+ byProject: {}
+ };
+
+ sessions.forEach(session => {
+ const projectId = session.projectId;
+ if (projectId && window.projectsMap?.has(projectId.toString())) {
+ if (!grouped.byProject[projectId]) {
+ grouped.byProject[projectId] = [];
+ }
+ grouped.byProject[projectId].push(session);
+ } else {
+ grouped.unassigned.push(session);
+ }
+ });
+
+ // Render projects first
+ let html = '';
+
+ for (const [projectId, projectSessions] of Object.entries(grouped.byProject)) {
+ const project = window.projectsMap.get(projectId);
+ if (!project) continue;
+
+ html += `
+
+
+
+ ${projectSessions.map(session => renderSessionCard(session)).join('')}
+
+
+ `;
+ }
+
+ // Render unassigned sessions
+ if (grouped.unassigned.length > 0) {
+ html += `
+
+
+
+ ${grouped.unassigned.map(session => renderSessionCard(session)).join('')}
+
+
+ `;
+ }
+
+ container.innerHTML = html;
+}
+
+function toggleProjectSection(projectId) {
+ const section = document.getElementById(`project-${projectId}`);
+ const icon = section.previousElementSibling.querySelector('.toggle-icon');
+
+ if (section.style.display === 'none') {
+ section.style.display = 'block';
+ icon.textContent = 'ā¼';
+ } else {
+ section.style.display = 'none';
+ icon.textContent = 'ā¶';
+ }
+}
+```
+
+**Step 2: Add context menu to session cards**
+
+Modify `renderSessionCard` to add the context menu trigger:
+
+```javascript
+function renderSessionCard(session) {
+ const project = session.projectId ? window.projectsMap?.get(session.projectId.toString()) : null;
+
+ return `
+
+
+ ${project ? `
${project.icon} ${escapeHtml(project.name)}
` : ''}
+
+
+ `;
+}
+```
+
+**Step 3: Commit**
+
+```bash
+git add public/claude-ide/sessions-landing.js
+git commit -m "feat: group sessions by project on landing page"
+```
+
+---
+
+## Task 10: Session Context Menu for Reassignment
+
+**Files:**
+- Modify: `public/claude-ide/sessions-landing.js`
+
+**Step 1: Add session context menu function**
+
+```javascript
+let currentSessionId = null;
+
+async function showSessionContextMenu(event, sessionId) {
+ event.preventDefault();
+ currentSessionId = sessionId;
+
+ const menu = document.getElementById('sessionContextMenu');
+
+ // Fetch suggestions
+ const suggestionsRes = await fetch(`/api/projects/suggestions?sessionId=${sessionId}`);
+ const suggestionsData = await suggestionsRes.json();
+
+ let menuHtml = `
+
+
+
+ `;
+
+ // Add suggestions
+ if (suggestionsData.success && suggestionsData.suggestions.length > 0) {
+ suggestionsData.suggestions.forEach(suggestion => {
+ const icon = getMatchIcon(suggestion.score);
+ const reasons = suggestion.reasons.join(', ');
+ menuHtml += `
+
+ `;
+ });
+
+ menuHtml += ``;
+ }
+
+ // Add "Show All Projects" option
+ menuHtml += `
+
+
+
+ `;
+
+ menu.innerHTML = menuHtml;
+ menu.style.left = event.pageX + 'px';
+ menu.style.top = event.pageY + 'px';
+ menu.style.display = 'block';
+}
+
+function getMatchIcon(score) {
+ if (score >= 90) return 'šÆ';
+ if (score >= 50) return 'š';
+ return 'š”';
+}
+
+async function moveSessionToProject(sessionId, projectId) {
+ try {
+ const response = await fetch(`/api/sessions/${sessionId}/move`, {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ projectId })
+ });
+
+ const result = await response.json();
+
+ if (result.success) {
+ showToast('Session moved', 'success');
+ await loadSessionsAndProjects();
+ } else {
+ showToast(result.error || 'Failed to move session', 'error');
+ }
+ } catch (error) {
+ console.error('Error moving session:', error);
+ showToast('Failed to move session', 'error');
+ }
+
+ hideSessionContextMenu();
+}
+
+function showAllProjectsForMove(sessionId) {
+ // Could show a modal with all projects
+ // For now, just alert the user to use projects page
+ showToast('Use the Projects page to manage assignments', 'info');
+ hideSessionContextMenu();
+}
+
+function hideSessionContextMenu() {
+ document.getElementById('sessionContextMenu').style.display = 'none';
+ currentSessionId = null;
+}
+
+// Close context menu on click outside
+document.addEventListener('click', (e) => {
+ if (!e.target.closest('#sessionContextMenu')) {
+ hideSessionContextMenu();
+ }
+});
+```
+
+**Step 2: Add context menu styles**
+
+```css
+#sessionContextMenu {
+ position: fixed;
+ background: var(--bg-primary);
+ border: 1px solid var(--border-color);
+ border-radius: 8px;
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
+ min-width: 250px;
+ z-index: 1000;
+ padding: 0.25rem 0;
+}
+
+.context-menu-label {
+ padding: 0.25rem 1rem;
+ font-size: 0.75rem;
+ color: var(--text-secondary);
+ text-transform: uppercase;
+ font-weight: 600;
+}
+
+.suggestion-reason {
+ font-size: 0.75rem;
+ color: var(--text-secondary);
+ margin-top: 0.125rem;
+}
+
+.session-project-badge {
+ position: absolute;
+ top: 0.5rem;
+ right: 0.5rem;
+ background: var(--accent-color);
+ color: white;
+ padding: 0.25rem 0.5rem;
+ border-radius: 4px;
+ font-size: 0.75rem;
+}
+
+.project-section {
+ margin-bottom: 2rem;
+}
+
+.project-section-header {
+ display: flex;
+ align-items: center;
+ gap: 0.75rem;
+ padding: 0.75rem 1rem;
+ background: var(--bg-secondary);
+ border: 1px solid var(--border-color);
+ border-radius: 8px;
+ cursor: pointer;
+ margin-bottom: 1rem;
+ transition: background 0.2s;
+}
+
+.project-section-header:hover {
+ background: var(--border-color);
+}
+
+.project-section-header.unassigned {
+ border-style: dashed;
+}
+
+.toggle-icon {
+ margin-left: auto;
+ transition: transform 0.2s;
+}
+
+.project-sessions {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
+ gap: 1rem;
+}
+```
+
+**Step 3: Commit**
+
+```bash
+git add public/claude-ide/sessions-landing.js public/claude-ide/sessions-landing.css
+git commit -m "feat: add session context menu for project reassignment"
+```
+
+---
+
+## Task 11: Database Migration Script
+
+**Files:**
+- Create: `scripts/migrate-to-projects.js`
+
+**Step 1: Create migration script**
+
+```javascript
+const { MongoClient, ObjectId } = require('mongodb');
+const path = require('path');
+
+// Configuration
+const MONGO_URI = process.env.MONGO_URI || 'mongodb://localhost:27017';
+const DB_NAME = process.env.DB_NAME || 'claude-web-interface';
+
+async function migrate() {
+ const client = new MongoClient(MONGO_URI);
+
+ try {
+ console.log('Connecting to MongoDB...');
+ await client.connect();
+ const db = client.db(DB_NAME);
+
+ const sessionsCollection = db.collection('sessions');
+ const projectsCollection = db.collection('projects');
+
+ console.log('Creating projects collection...');
+ await db.createCollection('projects');
+
+ console.log('Creating indexes...');
+ await projectsCollection.createIndex({ deletedAt: 1 });
+ await projectsCollection.createIndex({ name: 1 });
+
+ console.log('Finding unique project names from existing sessions...');
+ const uniqueProjects = await sessionsCollection.distinct('metadata.project');
+
+ console.log(`Found ${uniqueProjects.length} unique projects to migrate...`);
+
+ const icons = ['š', 'š±', 'šØ', 'š»', 'š§', 'š', 'šÆ', 'š®', 'ā”', 'š'];
+ const colors = ['#4a9eff', '#ff6b6b', '#51cf66', '#ffd43b', '#cc5de8', '#ff922b', '#20c997', '#339af0'];
+
+ let projectCount = 0;
+
+ for (const name of uniqueProjects) {
+ if (!name) continue;
+
+ console.log(`Migrating project: ${name}`);
+
+ const projectSessions = await sessionsCollection.find({
+ 'metadata.project': name
+ }).toArray();
+
+ if (projectSessions.length === 0) continue;
+
+ const paths = [...new Set(projectSessions.map(s => s.workingDir).filter(Boolean))];
+ const createdAt = projectSessions[0].createdAt || new Date();
+ const lastActivity = Math.max(...projectSessions.map(s => s.lastActivity || s.createdAt));
+
+ const randomIcon = icons[Math.floor(Math.random() * icons.length)];
+ const randomColor = colors[Math.floor(Math.random() * colors.length)];
+
+ const project = {
+ name,
+ description: `Migrated from existing sessions`,
+ icon: randomIcon,
+ color: randomColor,
+ path: paths[0] || '',
+ sessionIds: projectSessions.map(s => s._id),
+ createdAt,
+ lastActivity: new Date(lastActivity),
+ deletedAt: null
+ };
+
+ const result = await projectsCollection.insertOne(project);
+ console.log(` ā Created project with ID: ${result.insertedId}`);
+
+ // Update sessions with projectId
+ await sessionsCollection.updateMany(
+ { 'metadata.project': name },
+ { $set: { projectId: result.insertedId } }
+ );
+
+ console.log(` ā Updated ${projectSessions.length} sessions`);
+ projectCount++;
+ }
+
+ console.log(`\nā
Migration complete! Created ${projectCount} projects.`);
+
+ } catch (error) {
+ console.error('Migration failed:', error);
+ process.exit(1);
+ } finally {
+ await client.close();
+ }
+}
+
+// Run migration
+migrate();
+```
+
+**Step 2: Add migration to package.json**
+
+Add to `scripts` section:
+
+```json
+"scripts": {
+ "migrate:projects": "node scripts/migrate-to-projects.js"
+}
+```
+
+**Step 3: Commit**
+
+```bash
+git add scripts/migrate-to-projects.js package.json
+git commit -m "feat: add database migration script for projects"
+```
+
+**Step 4: Run migration**
+
+```bash
+npm run migrate:projects
+```
+
+**Step 5: Commit migration results**
+
+```bash
+git add .
+git commit -m "chore: run project migration"
+```
+
+---
+
+## Task 12: Add Route Handler for Projects Page
+
+**Files:**
+- Modify: `server.js`
+
+**Step 1: Add projects page route**
+
+Find the routes section (around line 70) and add:
+
+```javascript
+// Projects page
+app.get('/projects', (req, res) => {
+ if (req.session.userId) {
+ res.sendFile(path.join(__dirname, 'public', 'projects.html'));
+ } else {
+ res.redirect('/claude/');
+ }
+});
+```
+
+**Step 2: Add link to projects page in navigation**
+
+Modify the landing page header to include a "Projects" link:
+
+```javascript
+// In the landing page HTML or navigation component
+// Add: Projects
+```
+
+**Step 3: Commit**
+
+```bash
+git add server.js public/claude-landing.html
+git commit -m "feat: add projects page route and navigation link"
+```
+
+---
+
+## Task 13: Update Session Creation to Auto-Assign to Project
+
+**Files:**
+- Modify: `public/claude-ide/ide.js`
+- Modify: `server.js`
+
+**Step 1: Add projectId to session creation in IDE**
+
+Find the session creation code in ide.js and modify to include projectId if a project is selected:
+
+```javascript
+// When creating a new session
+async function createSession(sessionData) {
+ const currentProject = getCurrentProject(); // Get from project selector
+ const payload = {
+ ...sessionData,
+ projectId: currentProject?.id || null
+ };
+
+ const response = await fetch('/claude/api/claude/sessions', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify(payload)
+ });
+
+ // ... handle response
+}
+```
+
+**Step 2: Update server endpoint to handle projectId**
+
+Modify the session creation endpoint in server.js:
+
+```javascript
+app.post('/claude/api/claude/sessions', requireAuth, async (req, res) => {
+ try {
+ const { name, workingDir, projectId } = req.body;
+
+ // ... existing validation
+
+ // If projectId provided, verify it exists
+ if (projectId) {
+ const project = await projectsCollection.findOne({
+ _id: new ObjectId(projectId),
+ deletedAt: null
+ });
+
+ if (!project) {
+ return res.status(400).json({ error: 'Invalid project ID' });
+ }
+ }
+
+ const session = {
+ // ... existing fields
+ projectId: projectId ? new ObjectId(projectId) : null,
+ createdAt: new Date(),
+ lastActivity: new Date()
+ };
+
+ const result = await sessionsCollection.insertOne(session);
+
+ // Add session to project's sessionIds
+ if (projectId) {
+ await projectsCollection.updateOne(
+ { _id: new ObjectId(projectId) },
+ {
+ $push: { sessionIds: result.insertedId },
+ $set: { lastActivity: new Date() }
+ }
+ );
+ }
+
+ // ... rest of endpoint
+ } catch (error) {
+ // ... error handling
+ }
+});
+```
+
+**Step 3: Commit**
+
+```bash
+git add public/claude-ide/ide.js server.js
+git commit -m "feat: auto-assign new sessions to selected project"
+```
+
+---
+
+## Task 14: Testing and Documentation
+
+**Files:**
+- Create: `docs/projects-feature.md`
+
+**Step 1: Create documentation**
+
+```markdown
+# Projects Feature
+
+## Overview
+
+Projects are persistent containers that organize multiple Claude Code sessions.
+
+## Features
+
+### Creating Projects
+
+1. Navigate to `/projects`
+2. Click "+ New Project"
+3. Enter name and working directory (required)
+4. Optionally add description, icon, and color
+5. Click "Save Project"
+
+### Assigning Sessions to Projects
+
+**Automatic Assignment:**
+- When creating a session from the IDE with a project selected, it auto-assigns
+
+**Manual Assignment:**
+1. Go to sessions landing page (`/claude/`)
+2. Right-click on any session card
+3. Select "Move to Project" from context menu
+4. Choose from:
+ - šÆ **Suggestions** - Smart recommendations based on directory, recency, name
+ - š **All Projects** - Full project list
+
+### Managing Projects
+
+**Edit Project:**
+- Right-click project card ā Edit
+- Modify any field except name
+- Save changes
+
+**Delete Project:**
+- Right-click project card ā Move to Recycle Bin
+- Project and all sessions are soft-deleted
+- Can restore from recycle bin within 30 days
+
+**Recycle Bin:**
+- Click "šļø Recycle Bin" button on projects page
+- Restore deleted projects (restores all sessions too)
+- Permanently delete (cannot be undone)
+
+## API Reference
+
+### Projects
+
+- `GET /api/projects` - List all active projects
+- `POST /api/projects` - Create new project
+- `PUT /api/projects/:id` - Update project
+- `DELETE /api/projects/:id` - Soft delete project
+- `POST /api/projects/:id/restore` - Restore from recycle bin
+- `DELETE /api/projects/:id/permanent` - Permanent delete
+
+### Sessions
+
+- `POST /api/sessions/:id/move` - Move session to different project
+- `GET /api/projects/suggestions?sessionId=xxx` - Get project suggestions
+
+### Recycle Bin
+
+- `GET /api/recycle-bin` - List deleted items
+
+## Smart Suggestions
+
+The system suggests projects based on:
+
+1. **Directory Match (90 points)** - Exact path match
+2. **Subdirectory (50 points)** - Session path is under project path
+3. **Recent Use (20 points)** - Used today
+4. **Week-old Use (10 points)** - Used within last week
+5. **Name Similarity (15 points)** - Name overlap
+
+Suggestions with 90+ points show šÆ, 50-89 show š, lower show š”
+```
+
+**Step 2: Manual testing checklist**
+
+Test each feature and document results:
+
+```bash
+# 1. Create project
+curl -X POST http://localhost:3000/api/projects \
+ -H "Content-Type: application/json" \
+ -d '{"name":"Test Project","path":"/home/uroma/test"}'
+
+# 2. List projects
+curl http://localhost:3000/api/projects
+
+# 3. Create session with projectId
+curl -X POST http://localhost:3000/claude/api/claude/sessions \
+ -H "Content-Type: application/json" \
+ -d '{"name":"Test Session","workingDir":"/home/uroma/test","projectId":""}'
+
+# 4. Get suggestions
+curl "http://localhost:3000/api/projects/suggestions?sessionId="
+
+# 5. Move session
+curl -X POST http://localhost:3000/api/sessions//move \
+ -H "Content-Type: application/json" \
+ -d '{"projectId":""}'
+
+# 6. Delete project
+curl -X DELETE http://localhost:3000/api/projects/
+
+# 7. Restore project
+curl -X POST http://localhost:3000/api/projects//restore
+```
+
+**Step 3: Commit**
+
+```bash
+git add docs/projects-feature.md
+git commit -m "docs: add projects feature documentation"
+```
+
+---
+
+## Task 15: Final Integration and Cleanup
+
+**Files:**
+- Modify: `server.js`
+- Modify: `public/claude-ide/ide.js`
+
+**Step 1: Update ObjectId import**
+
+Ensure ObjectId is imported at the top of server.js:
+
+```javascript
+const { ObjectId } = require('mongodb');
+```
+
+**Step 2: Add error handling for missing collections**
+
+Add safety check in case projects collection doesn't exist yet:
+
+```javascript
+// At the start of API endpoints that use projectsCollection
+if (!db.collection('projects')) {
+ await db.createCollection('projects');
+}
+```
+
+**Step 3: Ensure all async functions have proper error handling**
+
+Check all new endpoints have try-catch blocks with proper error responses.
+
+**Step 4: Restart server and test**
+
+```bash
+# Stop existing server
+pkill -f "node.*server.js"
+
+# Start new server
+node server.js
+```
+
+**Step 5: Test the complete flow**
+
+1. Navigate to `/projects`
+2. Create a new project
+3. Go to `/claude/`
+4. Create a session (verify it's unassigned)
+5. Right-click session ā move to project
+6. Verify session appears under project
+7. Delete project ā verify in recycle bin
+8. Restore project ā verify it's back
+
+**Step 6: Final commit**
+
+```bash
+git add .
+git commit -m "feat: complete project and session organization feature
+
+- Persistent projects as first-class entities
+- Smart session assignment with suggestions
+- Context menu for session reassignment
+- Soft delete with recycle bin
+- Migration script for existing sessions
+- Full API and UI implementation
+
+Closes #1"
+```
+
+**Step 7: Push to remote**
+
+```bash
+git push origin feature/project-organization
+```
+
+---
+
+## Completion Criteria
+
+- [x] Projects collection created with proper indexes
+- [x] All CRUD endpoints working (create, read, update, delete)
+- [x] Soft delete implemented (deletedAt timestamp)
+- [x] Recycle bin UI functional
+- [x] Session reassignment via context menu
+- [x] Smart suggestions based on directory/recency/name
+- [x] Auto-assignment when creating sessions in project context
+- [x] Migration script successfully backfills existing data
+- [x] All tests passing
+- [x] Documentation complete
+
+**Ready for PR when all tasks complete!**
diff --git a/server.js b/server.js
index 4e6b2f8f..17212dd2 100644
--- a/server.js
+++ b/server.js
@@ -633,6 +633,74 @@ app.delete('/claude/api/claude/sessions/:id', requireAuth, (req, res) => {
}
});
+// Move session to different project
+app.post('/api/sessions/:id/move', requireAuth, (req, res) => {
+ try {
+ const { id } = req.params;
+ const { projectId } = req.body;
+
+ // Check if session exists (in-memory or historical)
+ let session = claudeService.sessions.get(id);
+ if (!session) {
+ // Check historical sessions
+ const historicalSessions = claudeService.loadHistoricalSessions();
+ session = historicalSessions.find(s => s.id === id);
+ }
+
+ if (!session) {
+ return res.status(404).json({ error: 'Session not found' });
+ }
+
+ // If projectId is provided, validate it exists and is not deleted
+ if (projectId !== null && projectId !== undefined) {
+ const validatedId = validateProjectId(projectId);
+ if (!validatedId) {
+ return res.status(400).json({ error: 'Invalid project ID' });
+ }
+
+ const project = db.prepare(`
+ SELECT id FROM projects
+ WHERE id = ? AND deletedAt IS NULL
+ `).get(validatedId);
+
+ if (!project) {
+ return res.status(404).json({ error: 'Project not found' });
+ }
+
+ // Update session's metadata with projectId
+ if (claudeService.sessions.get(id)) {
+ // Active session - update metadata
+ const activeSession = claudeService.sessions.get(id);
+ activeSession.metadata.projectId = validatedId;
+ } else {
+ // Historical session - update in database
+ db.prepare(`
+ INSERT OR REPLACE INTO sessions (id, projectId, deletedAt)
+ VALUES (?, ?, NULL)
+ `).run(id, validatedId);
+ }
+ } else {
+ // Move to unassigned (projectId = null)
+ if (claudeService.sessions.get(id)) {
+ // Active session - remove projectId from metadata
+ const activeSession = claudeService.sessions.get(id);
+ delete activeSession.metadata.projectId;
+ } else {
+ // Historical session - update in database
+ db.prepare(`
+ INSERT OR REPLACE INTO sessions (id, projectId, deletedAt)
+ VALUES (?, NULL, NULL)
+ `).run(id);
+ }
+ }
+
+ res.json({ success: true });
+ } catch (error) {
+ console.error('Error moving session:', error);
+ res.status(500).json({ error: 'Failed to move session' });
+ }
+});
+
// Preview Management - Start preview server
app.post('/claude/api/claude/sessions/:id/preview/start', requireAuth, async (req, res) => {
try {
@@ -1006,6 +1074,21 @@ app.delete('/api/projects/:id', requireAuth, (req, res) => {
WHERE id = ?
`).run(now, validatedId);
+ // Soft delete all sessions associated with this project
+ db.prepare(`
+ UPDATE sessions
+ SET deletedAt = ?
+ WHERE projectId = ?
+ `).run(now, validatedId);
+
+ // Also update in-memory sessions
+ for (const [sessionId, session] of claudeService.sessions.entries()) {
+ if (session.metadata.projectId === validatedId) {
+ // Mark as deleted in metadata
+ session.metadata.deletedAt = now;
+ }
+ }
+
res.json({ success: true });
} catch (error) {
console.error('Error soft deleting project:', error);
diff --git a/services/database.js b/services/database.js
index df00c094..ceb7f0f6 100644
--- a/services/database.js
+++ b/services/database.js
@@ -48,6 +48,26 @@ function initializeDatabase() {
CREATE INDEX IF NOT EXISTS idx_projects_name ON projects(name)
`);
+ // Create sessions table
+ db.exec(`
+ CREATE TABLE IF NOT EXISTS sessions (
+ id TEXT PRIMARY KEY,
+ projectId INTEGER NULL,
+ deletedAt TEXT NULL,
+ FOREIGN KEY (projectId) REFERENCES projects(id) ON DELETE SET NULL
+ )
+ `);
+
+ // Create index on projectId for efficient session queries
+ db.exec(`
+ CREATE INDEX IF NOT EXISTS idx_sessions_projectId ON sessions(projectId)
+ `);
+
+ // Create index on deletedAt for efficient soft-delete queries
+ db.exec(`
+ CREATE INDEX IF NOT EXISTS idx_sessions_deletedAt ON sessions(deletedAt)
+ `);
+
return db;
}