Files
headquarter/openspec/changes/repo-workspace/specs/spec.md
T
Fusion 6807f449b7 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 ✓
2026-05-19 13:43:49 +02:00

5.8 KiB

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:

{
  "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:

{
  "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:

{
  "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:

{
  "path": "src/main.py",
  "branch": "main",
  "content": "new content",
  "commit_message": "Update file",
  "author_name": "User",
  "author_email": "user@example.com"
}

Response 200:

{
  "commit_hash": "def789",
  "message": "Update file",
  "branch": "main"
}

Data Model

FileTreeEntry

interface FileTreeEntry {
  name: string;
  type: 'file' | 'directory';
  path: string;
  size?: number;
  mode?: string;
  last_commit?: {
    hash: string;
    message: string;
    author: string;
    date: string;
  };
}

Branch

interface Branch {
  name: string;
  is_default: boolean;
  last_commit?: {
    hash: string;
    message: string;
    date: string;
  };
}

FileContent

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:

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