feat: add commit panel and file status indicators to repo workspace

- Add CommitPanel component for viewing changed files and committing
- Show file status indicators (M/A/D/?) in file tree
- Integrate git status with workspace for real-time updates
- Add CSS styles for commit panel and status badges

Part of git-control change implementation.
This commit is contained in:
Fusion
2026-05-19 15:00:25 +02:00
parent 762e0de44c
commit 875594d66d
21 changed files with 2242 additions and 73 deletions
@@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-05-19
@@ -0,0 +1,269 @@
# Repository Workspace - Design
## Architecture
```
Project List
↓ (click project)
Repo Workspace (default view)
├─ Sidebar (200px)
│ ├─ Repo Selector (dropdown)
│ ├─ Branch Selector (dropdown)
│ └─ File Tree (scrollable)
│ ├─ 📁 src/
│ │ └─ 📄 main.py
│ ├─ 📁 tests/
│ └─ 📄 README.md
└─ Main Content
├─ Breadcrumbs: src > main.py
├─ Toolbar: [Edit] [Raw] [History]
└─ Content Area
├─ File View (syntax highlighted)
└─ Edit View (textarea with save)
```
## Page Layout
### Route: `/projects/:projectId`
This replaces the current placeholder and becomes the default project view.
### Layout Structure
```
┌─────────────────────────────────────────────────────────────┐
│ App Shell (Header + Nav) │
├─────────────────────────────────────────────────────────────┤
│ Project Header │
│ "My Project" [Repos] [History] [Settings]│
├─────────────────┬───────────────────────────────────────────┤
│ │ │
│ Sidebar │ Main Content │
│ ┌───────────┐ │ ┌─────────────────────────────────────┐ │
│ │ Repo ▼ │ │ │ Breadcrumbs: src > components │ │
│ ├───────────┤ │ ├─────────────────────────────────────┤ │
│ │ Branch ▼ │ │ │ [Edit] [History] [Blame] │ │
│ ├───────────┤ │ ├─────────────────────────────────────┤ │
│ │ 📁 src/ │ │ │ │ │
│ │ 📁 tests/ │ │ │ function hello() { │ │
│ │ 📄 README │ │ │ return "world"; │ │
│ │ ... │ │ │ } │ │
│ │ │ │ │ │ │
│ └───────────┘ │ └─────────────────────────────────────┘ │
│ │ │
└─────────────────┴───────────────────────────────────────────┘
```
## Data Flow
### 1. Page Load
```
Load /projects/:id
→ Fetch project details
→ Fetch repositories list
→ Fetch default branch file tree (first repo)
→ Render workspace
```
### 2. Repository Switch
```
Select repo from dropdown
→ Fetch branches list
→ Fetch default branch file tree
→ Reset file viewer
```
### 3. Branch Switch
```
Select branch from dropdown
→ Fetch file tree for branch
→ If viewing a file: re-fetch file content for branch
```
### 4. File Navigation
```
Click file in tree
→ Fetch file content (with syntax highlighting hint)
→ Show in viewer
→ Update breadcrumbs
```
## API Endpoints
### GET /projects/{project_id}/repositories/{repo_id}/files
List files at a path (like `ls` for git).
**Query Parameters:**
- `branch`: Branch name (default: repo's default branch)
- `path`: Directory path (default: root)
**Response:**
```json
{
"path": "src",
"branch": "main",
"entries": [
{
"name": "components",
"type": "directory",
"path": "src/components"
},
{
"name": "main.py",
"type": "file",
"path": "src/main.py",
"size": 1234,
"last_commit": {
"hash": "abc123",
"message": "Initial commit",
"date": "2024-01-01T00:00:00Z"
}
}
]
}
```
### GET /projects/{project_id}/repositories/{repo_id}/files/content
Get file content.
**Query Parameters:**
- `branch`: Branch name
- `path`: File path
**Response:**
```json
{
"path": "src/main.py",
"branch": "main",
"content": "function hello() {\n return 'world';\n}",
"size": 42,
"encoding": "utf-8",
"language": "python"
}
```
### GET /projects/{project_id}/repositories/{repo_id}/branches
List branches.
**Response:**
```json
{
"branches": [
{
"name": "main",
"is_default": true,
"last_commit": "abc123"
},
{
"name": "feature/new-thing",
"is_default": false,
"last_commit": "def456"
}
],
"default_branch": "main"
}
```
### POST /projects/{project_id}/repositories/{repo_id}/files/content
Update file content (for quick edits).
**Request Body:**
```json
{
"path": "src/main.py",
"branch": "main",
"content": "function hello() {\n return 'world!!!';\n}",
"commit_message": "Quick edit: update greeting",
"author_name": "User Name",
"author_email": "user@example.com"
}
```
## Components
### RepoWorkspace (Page)
- Orchestrates layout: sidebar + main content
- Manages repo/branch/file state
- Handles URL params (projectId, optional repoId)
### FileTree (Sidebar Component)
- Recursive tree view
- Expandable folders
- File icons based on extension
- Active file highlight
- Click to open file
### RepoSelector (Component)
- Dropdown of project repositories
- Shows active repo name
- Switch triggers repo change
### BranchSelector (Component)
- Dropdown of branches
- Shows active branch
- Switch triggers branch change
### FileViewer (Component)
- Syntax highlighted content
- Line numbers
- View/Edit toggle
- Breadcrumb navigation
### Breadcrumbs (Component)
- Path segments as clickable links
- Shows current file location
## State Management
### URL State
```
/projects/:projectId?repo=:repoId&branch=:branch&path=:path
```
- repo: selected repository ID
- branch: active branch name
- path: current file/directory path
### React State (per workspace)
```typescript
interface WorkspaceState {
projectId: string;
selectedRepoId: string | null;
selectedBranch: string;
currentPath: string;
selectedFile: string | null;
fileContent: string | null;
isEditing: boolean;
fileTree: FileTreeEntry[];
branches: Branch[];
repositories: Repository[];
}
```
## Implementation Order
1. **Backend file APIs** - List files, get content, list branches
2. **Backend update API** - Save file changes (commit)
3. **Project list clickable** - Link to workspace
4. **Workspace page shell** - Layout with sidebar + main
5. **File tree component** - Recursive directory listing
6. **File viewer component** - Content display
7. **Repo/branch selectors** - Dropdowns with state
8. **Edit mode** - Toggle + save
9. **URL state sync** - Sync selections to URL
10. **Polish** - Icons, syntax highlighting, error handling
## Error Handling
- **Repo not found**: Show error, allow selecting another
- **Branch not found**: Show error, default to main
- **File not found**: Show 404 in viewer
- **Permission denied**: Show auth error
- **Binary files**: Show "Binary file, cannot display" message
- **Large files**: Show warning, offer download
## Performance Considerations
- **Lazy load file tree**: Only expand directories when clicked
- **Cache file content**: Don't re-fetch if file hasn't changed
- **Debounce tree loading**: When switching branches, debounce
- **Virtual scrolling**: For large directories (100+ files)
- **Syntax highlighting**: Use lightweight highlighter, async load
@@ -0,0 +1,72 @@
# Repository Workspace - Default Project View
## Problem
Currently, clicking on a project shows a generic placeholder. Users need to navigate to repositories separately. There's no easy way to browse repository files or see project content at a glance.
## Solution
Create a **Repository Workspace** as the default project view that provides:
1. **File browser** for repositories - browse files and directories
2. **Branch selector** - switch between branches
3. **Mini file viewer** - view file contents with syntax highlighting
4. **Quick edit capability** - small edits without leaving the browser
5. **Repository overview** - see all repos in the project
This becomes the default view when clicking on a project, making the project the central workspace.
## Key Features
### Default Project View
- Clicking any project opens the repository workspace
- Shows first repo by default (or repo selector if multiple)
- File browser on the left, content viewer on the right
### File Browser
- Tree view of repository files and directories
- Expandable/collapsible folders
- Click file to view contents
- Breadcrumb navigation
### Branch Management
- Branch selector dropdown
- Shows current branch
- Lists all branches (local + remote)
- Switch branches to view different states
### File Viewer
- Syntax highlighting for common file types
- Line numbers
- View mode (read-only by default)
- Edit mode toggle for small changes
### Repository Navigation
- List all repositories in the project
- Quick switch between repos
- Repository cards with metadata (last commit, branch count)
## Benefits
- **Central hub**: Project becomes the main workspace, not just a container
- **Quick access**: See code immediately without extra clicks
- **Contextual**: Browse files while viewing commit history
- **Familiar**: Similar to GitHub/GitLab file browser
## Scope
### What stays:
- Existing repository list page (moves to sub-page)
- Git history visualization
- Repository creation/deletion
### What's new:
- Repository workspace page (default project view)
- File browser component
- File viewer component
- Branch selector component
- API endpoints for file operations
### What's changed:
- Project list items become clickable links
- Default project route shows workspace instead of placeholder
@@ -0,0 +1,257 @@
# Repository Workspace Specification
## Requirements
### Functional Requirements
1. **Default Project View**: Clicking a project opens the repository workspace
2. **File Browser**: Tree view of repository files and directories
3. **Branch Navigation**: Switch between branches to view different states
4. **File Viewer**: View file contents with syntax highlighting
5. **Quick Edit**: Make small changes and commit them
6. **Repository Switching**: Switch between repositories in a project
7. **Breadcrumb Navigation**: Show current file path with clickable segments
### Non-Functional Requirements
1. **Performance**: File tree loads in < 1 second
2. **Responsiveness**: UI remains responsive during git operations
3. **Usability**: Familiar interface similar to GitHub/GitLab
4. **Accessibility**: Keyboard navigation, screen reader support
## API Specification
### GET /projects/{project_id}/repositories/{repo_id}/files
List files in a directory.
**Query Parameters:**
- `branch` (optional): Branch name, defaults to repository default branch
- `path` (optional): Directory path, defaults to root
**Response 200:**
```json
{
"path": "src",
"branch": "main",
"entries": [
{
"name": "components",
"type": "directory",
"path": "src/components",
"mode": "040000"
},
{
"name": "main.py",
"type": "file",
"path": "src/main.py",
"size": 1234,
"mode": "100644",
"last_commit": {
"hash": "abc123",
"message": "Initial commit",
"author": "John Doe",
"date": "2024-01-01T00:00:00Z"
}
}
]
}
```
**Response 404:** Branch or path not found
### GET /projects/{project_id}/repositories/{repo_id}/files/content
Get file content.
**Query Parameters:**
- `branch` (required): Branch name
- `path` (required): File path
**Response 200:**
```json
{
"path": "src/main.py",
"branch": "main",
"content": "function hello() {\n return 'world';\n}",
"size": 42,
"encoding": "utf-8",
"language": "python",
"is_binary": false,
"last_commit": {
"hash": "abc123",
"message": "Initial commit",
"author": "John Doe",
"date": "2024-01-01T00:00:00Z"
}
}
```
**Response 404:** File not found
### GET /projects/{project_id}/repositories/{repo_id}/branches
List branches.
**Response 200:**
```json
{
"branches": [
{
"name": "main",
"is_default": true,
"last_commit": {
"hash": "abc123",
"message": "Initial commit",
"date": "2024-01-01T00:00:00Z"
}
}
],
"default_branch": "main"
}
```
### POST /projects/{project_id}/repositories/{repo_id}/files/content
Update file content.
**Request Body:**
```json
{
"path": "src/main.py",
"branch": "main",
"content": "new content",
"commit_message": "Update file",
"author_name": "User",
"author_email": "user@example.com"
}
```
**Response 200:**
```json
{
"commit_hash": "def789",
"message": "Update file",
"branch": "main"
}
```
## Data Model
### FileTreeEntry
```typescript
interface FileTreeEntry {
name: string;
type: 'file' | 'directory';
path: string;
size?: number;
mode?: string;
last_commit?: {
hash: string;
message: string;
author: string;
date: string;
};
}
```
### Branch
```typescript
interface Branch {
name: string;
is_default: boolean;
last_commit?: {
hash: string;
message: string;
date: string;
};
}
```
### FileContent
```typescript
interface FileContent {
path: string;
branch: string;
content: string;
size: number;
encoding: string;
language: string | null;
is_binary: boolean;
last_commit?: {
hash: string;
message: string;
author: string;
date: string;
};
}
```
## Frontend Specification
### URL Structure
```
/projects/:projectId → Default view (first repo, default branch)
/projects/:projectId?repo=:repoId → Specific repo
/projects/:projectId?repo=:repoId&branch=:branch&path=:path → Specific file
```
### Components
**RepoWorkspace**
- Layout: Sidebar (250px) + Main Content (flex)
- State: manages repo, branch, path, file selections
- Effects: sync URL params, fetch data
**FileTree**
- Props: entries, activePath, onFileClick, onDirectoryToggle
- Recursive rendering for nested directories
- Expand/collapse state per directory
**FileViewer**
- Props: content, language, path, isEditing, onEdit
- View mode: preformatted text with syntax highlighting
- Edit mode: textarea with save/cancel
**RepoSelector**
- Props: repositories, selectedRepoId, onSelect
- Dropdown with repo names
**BranchSelector**
- Props: branches, selectedBranch, onSelect
- Dropdown with branch names, default branch marked
### State Management
Use React state with URL synchronization:
```typescript
const [searchParams, setSearchParams] = useSearchParams();
const repoId = searchParams.get('repo');
const branch = searchParams.get('branch');
const path = searchParams.get('path');
```
## Error Codes
| Error Code | Description | User Message |
|------------|-------------|--------------|
| REPO_NOT_FOUND | Repository doesn't exist | "Repository not found" |
| BRANCH_NOT_FOUND | Branch doesn't exist | "Branch not found, using default" |
| FILE_NOT_FOUND | File path doesn't exist | "File not found" |
| BINARY_FILE | File is binary | "Cannot display binary file" |
| PERMISSION_DENIED | No access to file | "Permission denied" |
## Testing Strategy
### Backend Tests
- Test file listing for various paths
- Test file content retrieval
- Test branch listing
- Test file update/commit
- Test error cases (missing files, invalid branches)
### Frontend Tests
- Test file tree rendering
- Test file viewer display
- Test branch switching
- Test repo switching
- Test edit mode
- Test URL state sync
### Integration Tests
- End-to-end: Click project → browse files → view content → switch branch
@@ -0,0 +1,131 @@
# Repository Workspace - Tasks
## Phase 1: Backend File APIs
- [x] **Task 1.1**: Create git file utilities
- Create `src/utils/git_files.py`
- `list_tree()` - list files in directory using `git ls-tree`
- `get_file_content()` - get file content using `git show`
- `list_branches()` - list branches using `git branch`
- `commit_file()` - commit file changes using `git add` + `git commit`
- Add tests
- [x] **Task 1.2**: Add file listing endpoint
- Add `GET /projects/{id}/repositories/{id}/files` to git_repositories.py
- Query params: branch, path
- Returns FileTreeEntry list
- Handle errors (missing branch, missing path)
- [x] **Task 1.3**: Add file content endpoint
- Add `GET /projects/{id}/repositories/{id}/files/content` to git_repositories.py
- Query params: branch, path
- Returns FileContent with language detection
- Detect binary files
- [x] **Task 1.4**: Add branches endpoint
- Add `GET /projects/{id}/repositories/{id}/branches` to git_repositories.py
- Returns branch list with default branch marked
- [x] **Task 1.5**: Add file update endpoint
- Add `POST /projects/{id}/repositories/{id}/files/content` to git_repositories.py
- Body: path, branch, content, commit_message, author info
- Create commit with changes
- Return commit hash
## Phase 2: Project List Navigation
- [x] **Task 2.1**: Make project list clickable
- Update ProjectsPage to link to workspace
- Route: `/projects/:projectId`
- Remove placeholder, use workspace
- [x] **Task 2.2**: Update app navigation
- Ensure project routes are correct
- Add breadcrumb or back button
## Phase 3: Workspace Page Shell
- [x] **Task 3.1**: Create RepoWorkspace page
- Create `pages/repo-workspace.tsx`
- Layout: Sidebar + Main Content
- Fetch project repos on load
- Select first repo by default
- [x] **Task 3.2**: Create RepoSelector component
- Dropdown to switch between project repos
- Show active repo name
- Update URL when switching
- [x] **Task 3.3**: Create BranchSelector component
- Dropdown to switch branches
- Show active branch
- Mark default branch
- Fetch branches from API
## Phase 4: File Browser
- [x] **Task 4.1**: Create FileTree component
- Recursive tree view
- Expandable/collapsible folders
- File icons by extension
- Click to open file
- Active file highlight
- Fetch tree data from API
- [x] **Task 4.2**: Add file tree loading
- Load root on repo/branch change
- Lazy load subdirectories
- Show loading state
## Phase 5: File Viewer
- [x] **Task 5.1**: Create FileViewer component
- Display file content
- Line numbers
- Syntax highlighting (prismjs or similar)
- Breadcrumb navigation
- Show file metadata (size, last commit)
- [x] **Task 5.2**: Add edit mode
- Toggle between view/edit
- Textarea for editing
- Save button (calls update API)
- Cancel button
- Commit message input
## Phase 6: Integration & Polish
- [x] **Task 6.1**: Sync URL state
- Repo ID in URL
- Branch in URL
- Path in URL
- Parse on load, update on change
- [x] **Task 6.2**: Add error handling
- Repo not found
- Branch not found
- File not found
- Binary files
- Network errors
- [x] **Task 6.3**: Add CSS styles
- Workspace layout
- File tree styles
- File viewer styles
- Sidebar styles
- Responsive design
- [x] **Task 6.4**: Run quality gates
- Backend: ruff, mypy, pytest
- Frontend: typecheck, lint, build
## Phase 7: Route Updates
- [x] **Task 7.1**: Update router
- `/projects/:projectId` → RepoWorkspace (default)
- Move old project details to `/projects/:projectId/details` or remove
- Keep `/projects/:projectId/repositories` for repo management
- [x] **Task 7.2**: Update navigation
- Project list links to workspace
- Add "Manage Repositories" link in workspace