docs: add session branch selection design spec
- Design for branch dropdown in session creation - New local branch creation at clone time - Frontend/backend changes overview
This commit is contained in:
@@ -0,0 +1,155 @@
|
||||
# Session Branch Selection with New Branch Creation
|
||||
|
||||
## Summary
|
||||
|
||||
Replace the free-text branch input in the session creation form with a dropdown of available branches from the repository. Add the ability to create a new local branch at clone time by selecting "Create new branch..." from the dropdown.
|
||||
|
||||
## Context
|
||||
|
||||
The current session creation UI (`apps/web/src/pages/sessions.tsx`) has a free-text input for the branch name when "Clone fresh copy" mode is selected. Users must manually type the branch name, which is error-prone and doesn't show what branches are available.
|
||||
|
||||
The backend already has:
|
||||
- A `GET /projects/{project_id}/repositories/{repo_id}/branches` endpoint that returns all branches and the default branch
|
||||
- A `clone_repository` service that clones a specific branch
|
||||
- Branch creation APIs for the original repository
|
||||
|
||||
## Design
|
||||
|
||||
### Frontend Changes
|
||||
|
||||
#### 1. Branch API Integration
|
||||
|
||||
Add a new API function in `apps/web/src/api/git_repositories.ts`:
|
||||
|
||||
```typescript
|
||||
export interface Branch {
|
||||
name: string;
|
||||
is_default: boolean;
|
||||
last_commit: string | null;
|
||||
}
|
||||
|
||||
export interface BranchesResponse {
|
||||
branches: Branch[];
|
||||
default_branch: string;
|
||||
}
|
||||
|
||||
export async function listRepositoryBranches(
|
||||
projectId: string,
|
||||
repoId: string
|
||||
): Promise<BranchesResponse> {
|
||||
const response = await apiClient.get(
|
||||
`/projects/${projectId}/repositories/${repoId}/branches`
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
```
|
||||
|
||||
#### 2. UI State Management
|
||||
|
||||
In `apps/web/src/pages/sessions.tsx`, add state for:
|
||||
- `branches`: `Branch[]` — loaded when a repository is selected and clone mode is active
|
||||
- `isLoadingBranches`: `boolean`
|
||||
- `isCreatingNewBranch`: `boolean` — toggled when "Create new branch..." is selected
|
||||
- `newBranchName`: `string` — the name for the new branch
|
||||
- `baseBranch`: `string` — the base branch for the new branch
|
||||
|
||||
#### 3. Branch Loading
|
||||
|
||||
When a repository is selected and clone mode is "clone", fetch branches:
|
||||
- Call `listRepositoryBranches(selectedProject, selectedRepo)`
|
||||
- Set `baseBranch` to `default_branch` from the response
|
||||
- If the current `branch` state is not in the list, reset it to `default_branch`
|
||||
|
||||
#### 4. Branch Dropdown
|
||||
|
||||
Replace the free-text input with a `<select>`:
|
||||
- Options populated from `branches` state
|
||||
- Default branch marked visually: `"main (default)"`
|
||||
- Last option: `"Create new branch..."` (disabled separator style or as a real option)
|
||||
- When selected, set `isCreatingNewBranch = true`
|
||||
|
||||
#### 5. New Branch Form
|
||||
|
||||
When `isCreatingNewBranch` is true, show:
|
||||
- **New branch name** input (required, validated for valid git branch name)
|
||||
- **Base branch** dropdown (populated from `branches`, defaulting to `default_branch`)
|
||||
|
||||
#### 6. Form Submission
|
||||
|
||||
Update `handleCreate` to handle new branch creation:
|
||||
- If `isCreatingNewBranch` is true, pass `newBranchName` and `baseBranch` to the API
|
||||
- The `branch` parameter sent to the API should be:
|
||||
- `newBranchName` if creating a new branch
|
||||
- The selected existing branch otherwise
|
||||
|
||||
### Backend Changes
|
||||
|
||||
#### 1. Update `CreateInstanceRequest`
|
||||
|
||||
In `apps/api/src/api/tool_instances.py`, extend the request model:
|
||||
|
||||
```python
|
||||
class CreateInstanceRequest(BaseModel):
|
||||
model_config = {"extra": "ignore"}
|
||||
|
||||
tool_type_id: str = Field(description="UUID of the tool type to instantiate")
|
||||
display_name: str | None = Field(default=None, description="Optional display name")
|
||||
clone_mode: str = Field(default="mount", description="'mount' or 'clone'")
|
||||
branch: str | None = Field(default="main", description="Branch to clone")
|
||||
new_branch: str | None = Field(default=None, description="Create a new branch from 'branch' after clone")
|
||||
```
|
||||
|
||||
#### 2. Update Clone Logic
|
||||
|
||||
In `create_instance`, after cloning:
|
||||
- If `data.new_branch` is provided:
|
||||
1. Clone the `data.branch` (base branch) as usual
|
||||
2. Run `git -C <clone_path> checkout -b <new_branch>` to create the local branch
|
||||
3. Store `new_branch` in the `branch` field of the ToolInstance record
|
||||
|
||||
#### 3. Update `clone_repository` Service
|
||||
|
||||
No changes needed — it already clones a specific branch. The new branch creation happens after clone.
|
||||
|
||||
#### 4. Database Schema
|
||||
|
||||
No changes needed — the existing `branch` field on `ToolInstance` can store the new branch name.
|
||||
|
||||
### Data Flow
|
||||
|
||||
```
|
||||
User selects repo + "Clone fresh copy"
|
||||
→ Frontend fetches branches from GET /branches
|
||||
→ User selects "Create new branch..."
|
||||
→ User fills: newBranchName="feature-x", baseBranch="dev"
|
||||
→ Frontend sends: { branch: "dev", new_branch: "feature-x", ... }
|
||||
→ Backend clones "dev" branch
|
||||
→ Backend runs: git checkout -b feature-x
|
||||
→ Instance record stores branch="feature-x"
|
||||
→ Container starts with the new branch checked out
|
||||
```
|
||||
|
||||
### Error Handling
|
||||
|
||||
- **Branch fetch fails**: Show error, fallback to free-text input
|
||||
- **Invalid branch name**: Frontend validation (regex for valid git branch names)
|
||||
- **New branch creation fails**: Backend returns 400 with git error message
|
||||
- **Branch already exists locally**: Backend handles gracefully (git checkout -b will fail if branch exists)
|
||||
|
||||
### Testing
|
||||
|
||||
1. **Frontend**: Test branch dropdown loads correctly, "Create new branch" toggle works, form submission sends correct payload
|
||||
2. **Backend**: Test instance creation with `new_branch` parameter, verify git command runs correctly
|
||||
3. **Integration**: End-to-end test creating a session with a new branch
|
||||
|
||||
## Files Changed
|
||||
|
||||
- `apps/web/src/api/git_repositories.ts` — Add `listRepositoryBranches` function
|
||||
- `apps/web/src/pages/sessions.tsx` — Replace branch input with dropdown + new branch form
|
||||
- `apps/api/src/api/tool_instances.py` — Extend `CreateInstanceRequest` and clone logic
|
||||
- `apps/api/src/services/clone.py` — Add `create_local_branch` helper (optional)
|
||||
|
||||
## Trade-offs
|
||||
|
||||
- **Local branch only**: The new branch is created in the cloned workspace only, not pushed to the remote. This is intentional — it's a disposable work branch.
|
||||
- **No branch deletion**: When the instance is deleted, the branch is lost with the clone. This matches the "disposable" mental model.
|
||||
Reference in New Issue
Block a user