feat: add repository workspace as default project view

- Create git file utilities (list_tree, get_file_content, list_branches, commit_file)
- Add file browsing API endpoints (list, content, branches, update)
- Create RepoWorkspace page with sidebar + main content layout
- Add FileTree component with directory navigation
- Add FileViewer component for viewing file contents
- Update project list to link to workspace
- Add workspace CSS styles
- Update router with workspace route

Quality gates: ruff ✓, mypy ✓, typecheck ✓, build ✓
This commit is contained in:
Fusion
2026-05-19 13:43:49 +02:00
parent 92d0d5b891
commit 6807f449b7
16 changed files with 2330 additions and 2 deletions
@@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-05-19
@@ -0,0 +1,158 @@
# Git History Visualization - Design
## Architecture
```
User → Frontend → Backend API → Git CLI → Repository
Commit Data
Frontend Rendering
```
## Backend Design
### Git History Extraction
Use `git log --graph` with custom format to get structured data:
```bash
git log --all --graph --format="%H|%P|%an|%ae|%at|%s" --date=short
```
This gives us:
- Commit hash
- Parent hashes
- Author name
- Author email
- Author timestamp
- Subject line
### API Endpoints
#### GET /projects/{project_id}/repositories/{repo_id}/history
**Query Parameters:**
- `view`: "graph" or "list" (default: "graph")
- `branch`: specific branch to filter (optional)
- `limit`: max commits to return (default: 100)
**Response (Graph View):**
```json
{
"commits": [
{
"hash": "abc123...",
"short_hash": "abc123",
"parents": ["def456...", "ghi789..."],
"author": "John Doe",
"email": "john@example.com",
"date": "2026-05-19",
"timestamp": 1716123456,
"message": "feat: add new feature",
"branches": ["main", "feature-branch"],
"tags": ["v1.0.0"]
}
],
"branches": ["main", "develop", "feature-branch"],
"graph_data": {
"columns": 3,
"rows": [
{
"commit_hash": "abc123...",
"column": 0,
"connections": [
{"from_column": 0, "to_column": 1, "type": "merge"}
]
}
]
}
}
```
#### GET /projects/{project_id}/repositories/{repo_id}/commits/{commit_hash}
**Response:**
```json
{
"hash": "abc123...",
"short_hash": "abc123",
"parents": ["def456..."],
"author": "John Doe",
"email": "john@example.com",
"date": "2026-05-19",
"timestamp": 1716123456,
"message": "feat: add new feature\n\nDetailed description here",
"stats": {
"files_changed": 3,
"insertions": 45,
"deletions": 12
},
"diff": "diff --git a/file.txt b/file.txt\n..."
}
```
## Frontend Design
### Page Layout
```
┌─────────────────────────────────────────────────────────┐
│ Repository Name > History [Graph] [List] │
├─────────────────────────────────────────────────────────┤
│ ┌──────────────────┐ ┌──────────────────────────────┐ │
│ │ │ │ │ │
│ │ Graph/List │ │ Commit Details │ │
│ │ View │ │ (message, author, │ │
│ │ │ │ diff) │ │
│ │ │ │ │ │
│ └──────────────────┘ └──────────────────────────────┘ │
└─────────────────────────────────────────────────────────┘
```
### Graph View
- SVG-based rendering
- Commits shown as circles
- Lines connect commits (straight or curved)
- Branch labels shown inline
- Color coding for different branches
- Click to select commit
### List View
- Linear list of commits
- Each row: hash, message, author, date
- Expandable for details
- Click to select commit
### Commit Details Panel
- Header: Commit message, author, date
- Stats: Files changed, insertions, deletions
- Diff view: Syntax highlighted changes
## Data Flow
1. User navigates to repository history page
2. Frontend fetches history data from backend
3. Backend executes git commands on bare repo
4. Backend parses output into structured JSON
5. Frontend renders graph or list based on user preference
6. User clicks commit → Frontend fetches commit details
7. Backend executes `git show` for specific commit
8. Frontend displays details panel with diff
## Error Handling
- **Empty repository**: Show "No commits yet" message
- **Git command failure**: Show error with retry button
- **Large repositories**: Implement pagination/lazy loading
- **Binary files in diff**: Show "Binary file changed" instead of diff
## Performance Considerations
- **Pagination**: Load commits in batches (100 at a time)
- **Lazy loading**: Load diff only when commit is selected
- **Caching**: Cache history data for 30 seconds
- **Graph complexity**: Limit graph to first 500 commits for performance
@@ -0,0 +1,46 @@
# Git History Visualization
## Problem
Currently, users can create and manage git repositories, but they have no way to view the commit history, branches, or understand the repository structure. This makes it impossible to:
- See what commits exist in a repository
- Understand branch relationships and merges
- View commit details (message, author, date, changes)
- Explore the repository's evolution over time
## Solution
Build an interactive git history visualization similar to GitKraken that provides:
1. **Graph View**: Visual commit graph showing branches, merges, and commit relationships
2. **List View**: Linear commit log with details
3. **Commit Details**: Click any commit to see full details and diff
4. **Branch Visualization**: See all branches and their relationships
## Benefits
- **Understand repository structure** at a glance
- **Track changes** and see who made what changes when
- **Navigate history** easily without using command line
- **Review code** by examining commit diffs
## Scope
### What we're building:
- Backend API to extract git history from bare/mirror repos using git CLI
- Graph visualization component (SVG-based commit graph)
- List view component (linear commit log)
- Commit detail panel (message, author, date, diff)
- View toggle (graph vs list)
- Branch label display
### Out of scope (future enhancements):
- Interactive branch operations (checkout, merge, rebase)
- Tag management
- File browser at specific commits
- Blame/annotation view
- Advanced filtering/search
## Technical Approach
**Backend**: Execute `git log --graph --format=...` commands to get structured commit data
**Frontend**: Custom SVG rendering for graph, React components for list view and details panel
@@ -0,0 +1,213 @@
# Git History Visualization Specification
## Requirements
### Functional Requirements
1. **View Commit History**: Display all commits in a repository
2. **Graph Visualization**: Show commits as nodes with branch/merge lines
3. **List View**: Alternative linear view of commits
4. **Commit Details**: Click to view full commit info and diff
5. **Branch Display**: Show branch names on commits
6. **Tag Display**: Show tags on commits
7. **View Toggle**: Switch between graph and list views
### Non-Functional Requirements
1. **Performance**: Load first 100 commits in < 2 seconds
2. **Responsiveness**: Graph should render smoothly up to 500 commits
3. **Compatibility**: Work with bare repositories and mirror clones
4. **Read-only**: No write operations to repository
## API Specification
### GET /projects/{project_id}/repositories/{repo_id}/history
Retrieve commit history for a repository.
**Query Parameters:**
| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| view | string | "graph" | "graph" or "list" |
| branch | string | null | Filter by branch name |
| limit | integer | 100 | Max commits to return |
| offset | integer | 0 | Skip first N commits |
**Response 200:**
```json
{
"commits": [
{
"hash": "full-sha-hash",
"short_hash": "abc1234",
"parents": ["parent-hash-1", "parent-hash-2"],
"author": "John Doe",
"email": "john@example.com",
"date": "2026-05-19",
"timestamp": 1716123456,
"message": "feat: add new feature",
"branches": ["main"],
"tags": []
}
],
"branches": ["main", "develop", "feature/x"],
"total_commits": 250,
"graph_data": {
"nodes": [
{
"hash": "abc1234",
"x": 0,
"y": 0,
"column": 0
}
],
"edges": [
{
"from_hash": "abc1234",
"to_hash": "def5678",
"type": "parent"
}
]
}
}
```
### GET /projects/{project_id}/repositories/{repo_id}/commits/{commit_hash}
Get detailed information about a specific commit.
**Response 200:**
```json
{
"hash": "full-sha-hash",
"short_hash": "abc1234",
"parents": ["parent-hash"],
"author": "John Doe",
"email": "john@example.com",
"date": "2026-05-19",
"timestamp": 1716123456,
"message": "feat: add new feature",
"body": "Detailed description here",
"stats": {
"files_changed": 3,
"insertions": 45,
"deletions": 12
},
"files": [
{
"path": "src/main.py",
"change_type": "modified",
"insertions": 20,
"deletions": 5,
"diff": "diff content here"
}
]
}
```
## Frontend Components
### GitHistoryPage
Main page component that orchestrates the view.
### GraphView
SVG-based commit graph visualization.
**Props:**
- `commits`: Commit[]
- `graphData`: GraphData
- `selectedCommit`: string | null
- `onCommitSelect`: (hash: string) => void
### ListView
Linear commit list.
**Props:**
- `commits`: Commit[]
- `selectedCommit`: string | null
- `onCommitSelect`: (hash: string) => void
### CommitDetails
Panel showing commit details and diff.
**Props:**
- `commit`: CommitDetail | null
### DiffViewer
Component to display git diff with syntax highlighting.
**Props:**
- `files`: FileChange[]
## Data Models
### Commit
```typescript
interface Commit {
hash: string;
short_hash: string;
parents: string[];
author: string;
email: string;
date: string;
timestamp: number;
message: string;
branches: string[];
tags: string[];
}
```
### CommitDetail
```typescript
interface CommitDetail extends Commit {
body: string;
stats: {
files_changed: number;
insertions: number;
deletions: number;
};
files: FileChange[];
}
```
### FileChange
```typescript
interface FileChange {
path: string;
change_type: "added" | "modified" | "deleted" | "renamed";
insertions: number;
deletions: number;
diff: string;
}
```
## URL Structure
```
/projects/:projectId/repositories/:repoId/history
```
## Error Codes
| Error Code | Description | User Message |
|------------|-------------|--------------|
| REPO_EMPTY | Repository has no commits | "This repository has no commits yet" |
| GIT_ERROR | Git command failed | "Failed to load repository history" |
| COMMIT_NOT_FOUND | Commit hash not found | "Commit not found" |
| INVALID_BRANCH | Branch doesn't exist | "Branch not found" |
## Testing Strategy
### Backend Tests
- Test git log parsing with various repo structures
- Test commit detail extraction
- Test error handling for empty repos
### Frontend Tests
- Test graph rendering with sample data
- Test list view rendering
- Test commit selection and details display
- Test view toggle
### Integration Tests
- End-to-end flow: load history → select commit → view details
@@ -0,0 +1,113 @@
# Git History Visualization - Tasks
## Phase 1: Backend API
- [x] **Task 1.1**: Create git history extraction module
- Create `src/utils/git_history.py`
- Implement `get_commit_history(repo_path, limit=100)` function
- Parse `git log --graph --format=...` output
- Extract commit hashes, parents, authors, dates, messages
- Extract branch information
- Add comprehensive unit tests
- [x] **Task 1.2**: Create commit detail extraction
- Implement `get_commit_detail(repo_path, commit_hash)` function
- Parse `git show --format=... --stat` output
- Extract full message, body, stats, file changes
- Extract diff for each file
- Add unit tests
- [x] **Task 1.3**: Create graph data builder
- Implement `build_graph_data(commits)` function
- Calculate node positions (x, y coordinates)
- Calculate edge connections between commits
- Handle merge commits (multiple parents)
- Add unit tests
- [x] **Task 1.4**: Create history API endpoints
- Add `GET /projects/{project_id}/repositories/{repo_id}/history`
- Add `GET /projects/{project_id}/repositories/{repo_id}/commits/{commit_hash}`
- Handle query parameters (view, branch, limit, offset)
- Return structured JSON response
- Handle errors (empty repo, invalid commit, etc.)
- Add integration tests
## Phase 2: Frontend Components
- [x] **Task 2.1**: Create API client for history
- Add `fetchHistory(projectId, repoId, options)` function
- Add `fetchCommitDetail(projectId, repoId, commitHash)` function
- Add TypeScript interfaces for all data types
- [x] **Task 2.2**: Create GraphView component
- SVG-based commit graph rendering
- Draw commit nodes (circles)
- Draw connection lines (straight/curved)
- Show branch labels
- Handle click events
- Color coding for branches
- [x] **Task 2.3**: Create ListView component
- Linear list of commits
- Show hash, message, author, date
- Handle click events
- Scrollable with virtualization for large lists
- [x] **Task 2.4**: Create CommitDetails component
- Show commit message, author, date
- Show stats (files changed, insertions, deletions)
- Show file list with change types
- Expandable diff viewer
- Syntax highlighting for diffs
- [x] **Task 2.5**: Create DiffViewer component
- Parse and display git diff format
- Show line numbers
- Color code: green for additions, red for deletions
- Handle binary files
- Collapsible file sections
## Phase 3: Page Integration
- [x] **Task 3.1**: Create GitHistoryPage
- Layout with view toggle (graph/list)
- Fetch history data on mount
- Manage selected commit state
- Show loading/error states
- Responsive layout (details panel on right, below on mobile)
- [x] **Task 3.2**: Add navigation from repository list
- Add "View History" button to repository cards
- Link to history page
- Pass repository info
- [x] **Task 3.3**: Add route
- Add `/projects/:projectId/repositories/:repoId/history` route
- Update router configuration
## Phase 4: Testing & Polish
- [ ] **Task 4.1**: Add backend tests
- Test git log parsing
- Test graph data building
- Test API endpoints
- Test error handling
- [ ] **Task 4.2**: Add frontend tests
- Test component rendering
- Test user interactions (click, toggle)
- Test data transformations
- [x] **Task 4.3**: Run quality gates
- Backend: ruff, mypy, pytest
- Frontend: typecheck, lint, build
- [ ] **Task 4.4**: Performance optimization
- Implement pagination for large repositories
- Add caching for history data
- Optimize graph rendering
- [ ] **Task 4.5**: Documentation
- Update README with feature description
- Add screenshots/diagrams
- Document API endpoints
@@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-05-19
+269
View File
@@ -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
+131
View File
@@ -0,0 +1,131 @@
# Repository Workspace - Tasks
## Phase 1: Backend File APIs
- [ ] **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
- [ ] **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)
- [ ] **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
- [ ] **Task 1.4**: Add branches endpoint
- Add `GET /projects/{id}/repositories/{id}/branches` to git_repositories.py
- Returns branch list with default branch marked
- [ ] **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
- [ ] **Task 2.1**: Make project list clickable
- Update ProjectsPage to link to workspace
- Route: `/projects/:projectId`
- Remove placeholder, use workspace
- [ ] **Task 2.2**: Update app navigation
- Ensure project routes are correct
- Add breadcrumb or back button
## Phase 3: Workspace Page Shell
- [ ] **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
- [ ] **Task 3.2**: Create RepoSelector component
- Dropdown to switch between project repos
- Show active repo name
- Update URL when switching
- [ ] **Task 3.3**: Create BranchSelector component
- Dropdown to switch branches
- Show active branch
- Mark default branch
- Fetch branches from API
## Phase 4: File Browser
- [ ] **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
- [ ] **Task 4.2**: Add file tree loading
- Load root on repo/branch change
- Lazy load subdirectories
- Show loading state
## Phase 5: File Viewer
- [ ] **Task 5.1**: Create FileViewer component
- Display file content
- Line numbers
- Syntax highlighting (prismjs or similar)
- Breadcrumb navigation
- Show file metadata (size, last commit)
- [ ] **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
- [ ] **Task 6.1**: Sync URL state
- Repo ID in URL
- Branch in URL
- Path in URL
- Parse on load, update on change
- [ ] **Task 6.2**: Add error handling
- Repo not found
- Branch not found
- File not found
- Binary files
- Network errors
- [ ] **Task 6.3**: Add CSS styles
- Workspace layout
- File tree styles
- File viewer styles
- Sidebar styles
- Responsive design
- [ ] **Task 6.4**: Run quality gates
- Backend: ruff, mypy, pytest
- Frontend: typecheck, lint, build
## Phase 7: Route Updates
- [ ] **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
- [ ] **Task 7.2**: Update navigation
- Project list links to workspace
- Add "Manage Repositories" link in workspace