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>
This commit is contained in:
257
docs/plans/2025-01-19-project-session-organization-design.md
Normal file
257
docs/plans/2025-01-19-project-session-organization-design.md
Normal file
@@ -0,0 +1,257 @@
|
||||
# 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
|
||||
Reference in New Issue
Block a user