feat: workspace-first UI refresh - PR-1 backend endpoints
- Add FileService for workspace-scoped file operations
- Add GitOperations service for workspace-scoped git commands
- Add workspace_files API: GET/POST /workspaces/{id}/files
- Add workspace_git API: status, branches, commit, push, pull, fetch, checkout, history
- Add workspace_instances API: list instances per workspace
- Add top-level POST /workspaces/ (accepts repo_id directly)
- Enrich GET /projects/ with nested repositories and workspaces
- Register all new routers in main.py
- 23 tests passing (17 existing + 6 new)
Quality gates: ruff clean
This commit is contained in:
@@ -0,0 +1,366 @@
|
||||
# Spec: Workspace-First UI Refresh
|
||||
|
||||
## Status
|
||||
|
||||
| Field | Value |
|
||||
|---|---|
|
||||
| Phase | **Spec** |
|
||||
| Based on | [Proposal](proposal.md) |
|
||||
| Next | Design |
|
||||
|
||||
## Overview
|
||||
|
||||
Replace the old "Repository Workspace" (direct repo file browser) with a **Workspace-First** model. The workspace detail page becomes the primary work surface. Projects page shows inline repos + workspaces. Old `repo-workspace.tsx` is deleted.
|
||||
|
||||
## Decisions
|
||||
|
||||
| # | Question | Answer |
|
||||
|---|---|---|
|
||||
| 1 | URL structure | `/workspaces/:id` (flat) |
|
||||
| 2 | Sidebar nav order | Workspaces → Projects |
|
||||
| 3 | New Workspace flow | Inline form on project page |
|
||||
| 4 | Branch switching | Dropdown in workspace header |
|
||||
| 5 | Empty tool state | "Start a tool" prompt card |
|
||||
| 6 | Mobile tabs | Files / Git / Tools / Settings |
|
||||
| 7 | Git toolbar | Collapsible top bar on Files tab |
|
||||
| 8 | Git tab content | History, diff, full commit log |
|
||||
|
||||
## User Flows
|
||||
|
||||
### Flow 1: Open a Project
|
||||
|
||||
1. User clicks "Projects" in sidebar
|
||||
2. Sees project cards with inline repositories
|
||||
3. Each repo shows its workspaces as clickable cards
|
||||
4. User clicks a workspace → navigates to `/workspaces/:id`
|
||||
|
||||
### Flow 2: Work in a Workspace
|
||||
|
||||
1. User is on `/workspaces/:id`
|
||||
2. **Files tab** (default): File tree (left) + file viewer/editor (right). Git toolbar at top.
|
||||
3. User edits a file, commits via git toolbar
|
||||
4. **Git tab**: Full history, diff view, detailed commit log
|
||||
5. **Tools tab**: See running instances, click "Start Tool" → inline modal
|
||||
6. **Settings tab**: Sync workspace, rename, delete
|
||||
|
||||
### Flow 3: Create a Workspace
|
||||
|
||||
1. User on Projects page, expands a repo
|
||||
2. Clicks "+ New Workspace" next to a repo
|
||||
3. Inline form appears: name input, branch dropdown
|
||||
4. Submits → workspace created, appears in list
|
||||
|
||||
### Flow 4: Start a Tool
|
||||
|
||||
1. User on workspace detail, Tools tab
|
||||
2. If no instances: "Start a tool on this workspace" card
|
||||
3. If instances: list of cards + "Start Another" button
|
||||
4. Click → inline modal: tool type picker, config profile (optional)
|
||||
5. Submit → instance created, appears in list with status
|
||||
|
||||
## Backend API
|
||||
|
||||
### New Endpoints (workspace-scoped)
|
||||
|
||||
All endpoints operate on the workspace clone path (`workspace.path`).
|
||||
|
||||
```
|
||||
# Files
|
||||
GET /workspaces/{workspace_id}/files?path=&branch=
|
||||
→ List directory entries
|
||||
GET /workspaces/{workspace_id}/files/content?path=&branch=
|
||||
→ Get file content
|
||||
POST /workspaces/{workspace_id}/files/content
|
||||
Body: { path, content, message }
|
||||
→ Commit file change
|
||||
|
||||
# Git
|
||||
GET /workspaces/{workspace_id}/git/status
|
||||
→ { modified, added, deleted, untracked, branch }
|
||||
GET /workspaces/{workspace_id}/git/branches
|
||||
→ { branches, default_branch, current_branch }
|
||||
POST /workspaces/{workspace_id}/git/commit
|
||||
Body: { message, files? }
|
||||
→ Commit staged changes
|
||||
POST /workspaces/{workspace_id}/git/push
|
||||
→ Push current branch
|
||||
POST /workspaces/{workspace_id}/git/pull
|
||||
→ Pull current branch
|
||||
POST /workspaces/{workspace_id}/git/fetch
|
||||
→ Fetch from origin
|
||||
POST /workspaces/{workspace_id}/git/checkout
|
||||
Body: { branch }
|
||||
→ Switch branch
|
||||
GET /workspaces/{workspace_id}/git/history
|
||||
Query: ?path=&limit=50
|
||||
→ Commit history for file or entire repo
|
||||
|
||||
# Tools (instances on this workspace)
|
||||
GET /workspaces/{workspace_id}/instances
|
||||
→ List tool instances using this workspace
|
||||
POST /workspaces/{workspace_id}/instances
|
||||
Body: { tool_type_id, display_name?, config_profile_id? }
|
||||
→ Create instance on this workspace
|
||||
```
|
||||
|
||||
### Existing Endpoints (unchanged)
|
||||
|
||||
```
|
||||
GET /workspaces/
|
||||
POST /workspaces/ (body: { repo_id, name, branch })
|
||||
DELETE /workspaces/{id}
|
||||
POST /workspaces/{id}/sync
|
||||
PATCH /workspaces/{id}
|
||||
```
|
||||
|
||||
Note: `POST /workspaces/` now accepts `repo_id` directly instead of nested under `/projects/{pid}/repositories/{rid}/workspaces`.
|
||||
|
||||
### Modified Endpoints
|
||||
|
||||
```
|
||||
GET /projects/
|
||||
→ Now includes `repositories` array with `workspaces` sub-array
|
||||
```
|
||||
|
||||
## Database Schema
|
||||
|
||||
No changes. Existing `workspaces` table is sufficient.
|
||||
|
||||
## Frontend Routes
|
||||
|
||||
```
|
||||
/ → Dashboard (unchanged)
|
||||
/workspaces → All workspaces list (refreshed)
|
||||
/workspaces/:id → Workspace detail (NEW, replaces repo-workspace)
|
||||
/projects → Projects list (refreshed)
|
||||
/projects/:id → Project detail with repos + workspaces (NEW)
|
||||
/sessions → Sessions list (unchanged)
|
||||
/settings → Settings (unchanged)
|
||||
```
|
||||
|
||||
## UI Components
|
||||
|
||||
### WorkspaceDetailPage (`/workspaces/:id`)
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────────────────────────┐
|
||||
│ Breadcrumb: Projects > {project} > {repo} > {workspace} │
|
||||
│ [Branch ▼ main] [Sync] [Start Tool] [Settings] │
|
||||
├──────────────────────────────────────────────────────────────┤
|
||||
│ Tab bar: [Files] [Git] [Tools] [Settings] │
|
||||
├──────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ {Active Tab Content} │
|
||||
│ │
|
||||
└──────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
#### Files Tab (default)
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────────────────────────┐
|
||||
│ Git Toolbar (collapsible) │
|
||||
│ [Modified: 3] [Staged: 2] [Commit ▼] [Push] [Pull] [Fetch] │
|
||||
├──────────────┬───────────────────────────────────────────────┤
|
||||
│ │ │
|
||||
│ File Tree │ File Viewer / Editor │
|
||||
│ (workspace │ │
|
||||
│ path) │ Breadcrumbs: src > utils > helpers.ts │
|
||||
│ │ │
|
||||
│ 📁 src/ │ [Edit] [History] │
|
||||
│ 📄 README │ │
|
||||
│ │ export function ... │
|
||||
│ │ │
|
||||
└──────────────┴───────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**Git Toolbar**: Collapsible bar above file content. Shows:
|
||||
- Status counters: Modified, Added, Deleted, Untracked
|
||||
- Commit button (with message input when expanded)
|
||||
- Push, Pull, Fetch buttons
|
||||
- Branch selector dropdown
|
||||
|
||||
#### Git Tab
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────────────────────────┐
|
||||
│ Branch: [main ▼] [New Branch] [Merge] [Compare] │
|
||||
├──────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ Commit History │
|
||||
│ ┌────────────────────────────────────────────────────┐ │
|
||||
│ │ ● abc123 Fix auth middleware │ │
|
||||
│ │ ● def456 Add user profile page │ │
|
||||
│ │ ● 789abc Initial commit │ │
|
||||
│ └────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ [Show Diff] [Checkout] [Revert] │
|
||||
│ │
|
||||
└──────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
#### Tools Tab
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────────────────────────┐
|
||||
│ Active Tool Instances │
|
||||
│ │
|
||||
│ ┌─────────────┐ ┌─────────────┐ [+ Start Tool] │
|
||||
│ │ Code Server │ │ Terminal │ │
|
||||
│ │ ● Running │ │ ● Stopped │ │
|
||||
│ │ [Open] [Stop│ │ [Start] [×] │ │
|
||||
│ └─────────────┘ └─────────────┘ │
|
||||
│ │
|
||||
│ ─ or ─ │
|
||||
│ │
|
||||
│ ┌────────────────────────────────────────────────────┐ │
|
||||
│ │ No tools running on this workspace │ │
|
||||
│ │ Start a tool to begin coding │ │
|
||||
│ │ [Start Tool] │ │
|
||||
│ └────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
└──────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
#### Settings Tab
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────────────────────────┐
|
||||
│ Workspace Settings │
|
||||
│ │
|
||||
│ Name: [my-feature-branch ] │
|
||||
│ Branch: main (tracks origin/main) │
|
||||
│ Path: /data/working-copies/{repo-id}/{name} │
|
||||
│ Created: 2024-01-15 │
|
||||
│ Last Sync: 2024-01-20 14:32 │
|
||||
│ │
|
||||
│ [Rename] [Sync Now] [Delete Workspace] │
|
||||
│ │
|
||||
└──────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### ProjectsPage (`/projects`)
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────────────────────────┐
|
||||
│ Projects [+ New Project] │
|
||||
├──────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ ┌────────────────────────────────────────────────────┐ │
|
||||
│ │ My Web App │ │
|
||||
│ │ A full-stack application │ │
|
||||
│ ├────────────────────────────────────────────────────┤ │
|
||||
│ │ Repositories: │ │
|
||||
│ │ │ │
|
||||
│ │ ▼ frontend (git@github.com:me/frontend.git) │ │
|
||||
│ │ ┌──────────┐ ┌─────────────┐ [+ New Workspace] │ │
|
||||
│ │ │ main │ │ feature-ui │ │ │
|
||||
│ │ │ ● 2 inst │ │ ● 0 inst │ │ │
|
||||
│ │ └──────────┘ └─────────────┘ │ │
|
||||
│ │ │ │
|
||||
│ │ ▶ backend (git@github.com:me/backend.git) │ │
|
||||
│ │ ┌──────────┐ [+ New Workspace] │ │
|
||||
│ │ │ main │ │ │
|
||||
│ │ │ ● 1 inst │ │ │
|
||||
│ │ └──────────┘ │ │
|
||||
│ │ │ │
|
||||
│ │ [Edit Project] [Delete] │ │
|
||||
│ └────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
└──────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**Workspace Card**: Small card showing:
|
||||
- Name
|
||||
- Status badge (ready/syncing/error)
|
||||
- Instance count (dot + number)
|
||||
- Click navigates to `/workspaces/:id`
|
||||
|
||||
**New Workspace Button**: Inline form on click:
|
||||
```
|
||||
[Name: __________] [Branch: main ▼] [Create] [Cancel]
|
||||
```
|
||||
|
||||
### Mobile Layout
|
||||
|
||||
Bottom tab bar (4 tabs, always visible):
|
||||
|
||||
```
|
||||
┌────────────────────────────────────┐
|
||||
│ {Tab Content - full screen} │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
├────────────────────────────────────┤
|
||||
│ 📁 Files 🔀 Git 🛠 Tools ⚙ Settings│
|
||||
└────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**Files tab**: File tree full screen, tap file → viewer overlay
|
||||
**Git tab**: Commit history list, tap commit → diff overlay
|
||||
**Tools tab**: Instance cards stacked vertically
|
||||
**Settings tab**: Same as desktop settings, scrollable
|
||||
|
||||
## State & Data Flow
|
||||
|
||||
### Workspace Detail Page
|
||||
|
||||
```
|
||||
useWorkspace(workspaceId) → fetch /workspaces/{id}
|
||||
useWorkspaceFiles(workspaceId, path?, branch?) → fetch /workspaces/{id}/files
|
||||
useWorkspaceGitStatus(workspaceId) → fetch /workspaces/{id}/git/status
|
||||
useWorkspaceInstances(workspaceId) → fetch /workspaces/{id}/instances
|
||||
```
|
||||
|
||||
All hooks poll/refetch on:
|
||||
- Tab switch
|
||||
- User action (commit, push, etc.)
|
||||
- 30s background refresh
|
||||
|
||||
### Projects Page
|
||||
|
||||
```
|
||||
useProjects() → fetch /projects/
|
||||
useProjectWorkspaces(projectId) → derived from project.repositories.workspaces
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
| Scenario | UX |
|
||||
|---|---|
|
||||
| Workspace not found | 404 page with "Workspace not found" + link to workspaces |
|
||||
| Git operation fails | Toast with git stderr, retry button |
|
||||
| File read fails | "File not found" in viewer, check if on correct branch |
|
||||
| No tool types available | "No tools configured" + link to Tool Workshop |
|
||||
| Workspace path missing | "Workspace files not found — try syncing" |
|
||||
|
||||
## Accessibility
|
||||
|
||||
- Tab bar: `role="tablist"`, keyboard arrow navigation
|
||||
- File tree: `role="tree"`, arrow key expansion
|
||||
- Git toolbar: All buttons have `aria-label`
|
||||
- Focus management: Modal traps focus, returns on close
|
||||
|
||||
## Performance
|
||||
|
||||
- File tree: Virtualized for repos > 1000 files
|
||||
- Git history: Paginated (50 commits per page)
|
||||
- Image files: Lazy loaded in viewer
|
||||
- Polling: 30s for instances, 10s for git status when visible
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
- [ ] `repo-workspace.tsx` and related components deleted
|
||||
- [ ] `/projects/:id` route shows project detail, not file browser
|
||||
- [ ] `/workspaces/:id` route shows workspace detail page
|
||||
- [ ] File browser reads from workspace clone path
|
||||
- [ ] File viewer/editor works on workspace files
|
||||
- [ ] Git toolbar on Files tab supports commit/push/pull/fetch
|
||||
- [ ] Git tab shows commit history with diff
|
||||
- [ ] Tools tab lists instances + spawn modal
|
||||
- [ ] Settings tab shows workspace info + rename/sync/delete
|
||||
- [ ] Projects page shows inline repos + workspace cards
|
||||
- [ ] Inline "New Workspace" form on project page
|
||||
- [ ] Mobile: 4-tab bottom navigation
|
||||
- [ ] All existing tests pass or updated
|
||||
- [ ] ruff clean, TypeScript clean, eslint clean
|
||||
Reference in New Issue
Block a user