Files
SuperCharged-Claude-Code-Up…/docs/plans/2025-01-19-project-session-organization-design.md
uroma 0dd2083556 Initial commit: Obsidian Web Interface for Claude Code
- Full IDE with terminal integration using xterm.js
- Session management with local and web sessions
- HTML preview functionality
- Multi-terminal support with session picker

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-19 16:29:44 +00:00

258 lines
7.3 KiB
Markdown

# Project and Session Organization Design
**Date:** 2025-01-19
**Status:** Approved
**Author:** Claude (with user collaboration)
## Overview
Introduce persistent projects as first-class entities that contain multiple sessions, with intelligent assignment, reorganization, and soft-delete capabilities.
## Architecture
### Data Model
**Projects Collection**
```javascript
{
_id: ObjectId,
name: "My API Project",
description: "REST API development",
icon: "🚀",
color: "#4a9eff",
path: "/home/uroma/api",
sessionIds: [ObjectId, ...],
createdAt: Date,
lastActivity: Date,
deletedAt: Date | null // null = active, Date = in recycle bin
}
```
**Sessions Collection (Updated)**
```javascript
{
...
projectId: ObjectId | null, // null = unassigned
deletedAt: Date | null
}
```
### API Endpoints
| Method | Endpoint | Description |
|--------|----------|-------------|
| GET | `/api/projects` | List active projects |
| POST | `/api/projects` | Create project |
| PUT | `/api/projects/:id` | Update project |
| DELETE | `/api/projects/:id` | Soft delete (move to recycle bin) |
| POST | `/api/projects/:id/restore` | Restore from bin |
| DELETE | `/api/projects/:id/permanent` | Permanent delete |
| GET | `/api/recycle-bin` | List deleted items |
| POST | `/api/sessions/:id/move` | Move session to different project |
## UI Components
### Projects Page (`/projects`)
- **Header**: Title, "+ New Project" button, search bar
- **Project Grid**: Cards with icon, name, description, path, session count, last activity
- **Context Menu**: Edit, move to recycle bin
- **Empty State**: "No projects yet" with CTA
### Enhanced Landing Page (`/claude/`)
Projects as top-level, sessions nested inside:
- Collapsible project sections
- "Unassigned Sessions" section at bottom
- Session cards show project badge
- Right-click for context menu
### Session Context Menu
```
┌─────────────────────────┐
│ Open in IDE │
│ ─────────────────────── │
│ Move to Project ▶ │
│ ├── 🚀 My API (🎯 95%)│
│ ├── 📱 Mobile App │
│ ├── ──────────────────│
│ └── Show All Projects │
│ ─────────────────────── │
│ Duplicate │
│ Delete │
└─────────────────────────┘
```
### Recycle Bin
- Accessible from projects page header
- Shows deleted projects with faded sessions
- "Restore" and "Delete Permanently" buttons per item
## Smart Assignment Algorithm
### Auto-Assignment
When creating a session from the IDE project selector:
1. User selects project → `projectId` stored
2. Session added to project's `sessionIds` array
3. Project `lastActivity` updated
### Smart Suggestions
Calculated when moving sessions:
```javascript
function getSuggestions(session, allProjects) {
const suggestions = [];
for (const project of allProjects) {
let score = 0;
let 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({ project, score, reasons });
}
}
return suggestions.sort((a, b) => b.score - a.score).slice(0, 3);
}
```
**Visual Indicators:**
- 🎯 90%+ match → Same directory
- 📂 50-89% match → Subdirectory or recent use
- 💡 10-49% match → Similar name or recently used
## Recycle Bin System
### Soft Delete Flow
**Delete:**
```javascript
// Mark project as deleted
await db.projects.updateOne(
{ _id: projectId },
{ $set: { deletedAt: new Date() } }
);
// Soft delete all sessions in project
await db.sessions.updateMany(
{ projectId },
{ $set: { deletedAt: new Date() } }
);
```
**Restore:**
```javascript
await db.projects.updateOne({ _id }, { $set: { deletedAt: null } });
await db.sessions.updateMany({ projectId }, { $set: { deletedAt: null } });
```
**Permanent Delete:**
```javascript
await db.projects.deleteOne({ _id });
await db.sessions.deleteMany({ projectId });
```
## Edge Cases
| Scenario | Behavior |
|----------|----------|
| Reassigning from deleted project | Session becomes unassigned (projectId = null) |
| Deleting last session in project | Project persists with 0 sessions |
| Two projects with same path | Both shown as suggestions with equal score |
| Local CLI sessions | Can be assigned, path read from info.json |
| Moving to deleted project | Blocked - deleted projects excluded from menu |
| Concurrent edits | Last write wins (MongoDB atomic updates) |
## Implementation
### File Structure
```
server.js (add endpoints)
public/claude-ide/
├── projects.html (new)
├── projects.js (new)
├── projects.css (new)
└── sessions-landing.js (modify)
context-menu.js (new shared component)
```
### Migration Script
```javascript
// 1. Create projects collection
db.createCollection('projects');
// 2. Find unique project names from existing sessions
const uniqueProjects = await db.sessions.distinct('metadata.project');
// 3. Create project for each unique name
for (const name of uniqueProjects) {
const projectSessions = await db.sessions.find({ 'metadata.project': name }).toArray();
const paths = [...new Set(projectSessions.map(s => s.workingDir).filter(Boolean))];
await db.projects.insertOne({
name,
description: '',
icon: getRandomIcon(),
color: getRandomColor(),
path: paths[0] || '',
sessionIds: projectSessions.map(s => s._id),
createdAt: projectSessions[0].createdAt,
lastActivity: Math.max(...projectSessions.map(s => s.lastActivity)),
deletedAt: null
});
}
// 4. Update sessions with projectId
for (const project of await db.projects.find().toArray()) {
await db.sessions.updateMany(
{ 'metadata.project': project.name },
{ $set: { projectId: project._id } }
);
}
```
## Testing Checklist
- [ ] Create project via inline form
- [ ] Auto-assign session on creation
- [ ] Smart suggestions calculate correctly
- [ ] Move session via context menu
- [ ] Delete project → goes to recycle bin
- [ ] Restore project from bin
- [ ] Permanent delete works
- [ ] Unassigned sessions display correctly
- [ ] Migration backfills existing data
- [ ] Icon/color randomization works
- [ ] Path validation on project creation