feat: make session creation a sequential workflow

- Refactor CreateSessionForm into step-by-step workflow
- Steps unlock sequentially: Project → Repository → Tool → Clone Mode → Branch
- Add visual step indicators with numbered badges
- Disable controls until prerequisites are met
- Add CSS for workflow step styling
This commit is contained in:
2026-05-24 10:14:39 +00:00
parent 5cec4a7a6f
commit 96c8dd7402
10 changed files with 813 additions and 57 deletions
+195
View File
@@ -0,0 +1,195 @@
---
name: sift-backlog
description: Triage and organize backlog tasks into actionable plans. Use when asked to review the backlog, prioritize tasks, create plans from backlog items, or move tasks from backlog to open status. Handles the full workflow of listing backlog tasks, grouping related tasks into plans, setting priorities and dependencies, activating plans, and changing task status from backlog to open.
---
# Sift Backlog
Triage backlog tasks: prioritize, group into plans, set dependencies, and activate.
## Overview
1. List backlog tasks (`sf task backlog`)
2. Clarify and enrich each task (titles, descriptions)
3. Identify groupings and create draft plans
4. Add tasks to plans and set dependencies
5. Activate plans
6. Set task status to open
## Workflow
### Step 1: List Backlog Tasks
```bash
sf task backlog
```
### Step 2: Clarify and Enrich Tasks
Backlog tasks often have only a brief title with no description. Before organizing, ensure each task is well-defined.
**For each task, evaluate:**
- Is the title clear and actionable?
- Is there a description? Check with `sf task describe <task-id> --show`
- Is the scope unambiguous?
**If the title is unclear**, update it:
```bash
sf update <task-id> --title "Clear, actionable title"
```
**Add a description** with context, scope, and acceptance criteria:
```bash
sf task describe <task-id> --content "Description with:
- What needs to be done
- Why it matters
- Acceptance criteria
- Any relevant context"
```
**Use your best judgment** to interpret tasks and make reasonable decisions about scope, grouping, and priority. You have context about the codebase, project patterns, and typical development practices—leverage this knowledge rather than deferring to the user for routine decisions.
**Only ask the user for clarity when absolutely necessary:**
- The task is fundamentally ambiguous (multiple mutually exclusive interpretations)
- Critical business logic or user-facing behavior that could go wrong in meaningful ways
- External dependencies or integrations you cannot verify
**Do NOT ask about:**
- Implementation details you can reasonably infer
- Priority or grouping decisions—use your judgment
- Standard development practices (testing, code style, etc.)
- Tasks where a reasonable interpretation exists
### Step 3: Create Draft Plans
Group related tasks into plans using your best judgment. Plans start as drafts (tasks won't be dispatched until activated).
**Grouping guidance:**
- Group tasks that share a common theme, feature area, or goal
- Consider technical dependencies when grouping (tasks that touch the same files/modules)
- Separate unrelated work into distinct plans for parallel execution
- Don't over-group—if tasks are truly independent, separate plans enable better parallelism
- Don't under-group—related tasks benefit from shared context and coordinated execution
```bash
sf plan create --title "Plan Name"
```
**Example:**
```bash
sf plan create --title "Authentication Improvements"
# Output: Created plan el-abc123
```
### Step 4: Add Tasks to Plans
```bash
sf plan add-task <plan-id> <task-id>
```
**Example:**
```bash
sf plan add-task el-abc123 el-task1
sf plan add-task el-abc123 el-task2
```
### Step 5: Set Dependencies Between Tasks
Use `blocks` dependency when one task must complete before another can start.
```bash
sf dependency add <blocked-id> <blocker-id> --type blocks
```
**Semantics:** The first ID is blocked BY the second ID. The blocker must complete first.
**Example:** Task 2 can't start until Task 1 completes:
```bash
sf dependency add el-task2 el-task1 --type blocks
```
### Step 6: Update Priorities
Set priorities based on your assessment of impact, urgency, and dependencies. Use your judgment—you don't need user confirmation for routine prioritization.
**Priority guidance:**
- **Critical (1):** Blocking issues, security vulnerabilities, production bugs
- **High (2):** Important features with deadlines, significant user impact
- **Medium (3):** Standard feature work, most tasks default here
- **Low (4):** Nice-to-haves, minor improvements, tech debt
- **Minimal (5):** Backlog cleanup, documentation, exploratory work
```bash
sf update <task-id> --priority <1-5>
```
| Value | Level |
| ----- | -------- |
| 1 | Critical |
| 2 | High |
| 3 | Medium |
| 4 | Low |
| 5 | Minimal |
### Step 7: Activate Plans
Once tasks are organized with dependencies set, activate plans to enable dispatch.
```bash
sf plan activate <plan-id>
```
### Step 8: Set Task Status to Open
Move tasks from backlog to open so they become ready for work.
```bash
sf update <id> --status open
```
## Other Actions
**Close obsolete tasks:**
```bash
sf task close <id> --reason "Won't do: <reason>"
```
**Defer tasks:**
```bash
sf task defer <id> --until <date>
```
**View existing plans:**
```bash
sf plan list
```
**View tasks in a plan:**
```bash
sf plan tasks <plan-id>
```
## Tips
- **Use your best judgment** for grouping, prioritization, and task interpretation—don't defer routine decisions to the user
- **Only escalate to the user** when ambiguity is fundamental and could lead to wasted work (mutually exclusive interpretations, critical business decisions)
- Make reasonable inferences about implementation details, scope, and priority based on codebase context
- Create plans before setting dependencies to avoid dispatch race conditions
- Always activate plans after dependencies are set
- Focus on oldest backlog items first (sorted by creation date)
- Every task should have a clear title and description before activation
- When uncertain about a minor detail, make a reasonable choice and document it in the task description—workers can ask if needed
+1
View File
@@ -48,3 +48,4 @@ apps/web/dist/
# OS
.DS_Store
Thumbs.db
/.stoneforge/.worktrees/
+2
View File
@@ -0,0 +1,2 @@
84102
1779616938758
+6
View File
@@ -0,0 +1,6 @@
# Runtime data
*.db
*.db-journal
*.db-wal
*.db-shm
daemon-state.json
+20
View File
@@ -0,0 +1,20 @@
# Stoneforge Configuration
database: stoneforge.db
sync:
auto_export: true
elements_file: elements.jsonl
dependencies_file: dependencies.jsonl
playbooks:
paths:
- playbooks
identity:
mode: soft
merge:
auto_merge: true
target_branch: null
require_approval: false
workflow:
preset: auto
agents:
permission_model: unrestricted
+1
View File
@@ -0,0 +1 @@
{"blockedId":"el-1of","blockerId":"el-258","type":"parent-child","createdAt":"2026-05-24T09:44:58.759Z","createdBy":"el-2jua"}
File diff suppressed because one or more lines are too long
+112 -57
View File
@@ -80,7 +80,11 @@ export const CreateSessionForm = ({
const loadBranches = async () => {
setIsLoadingBranches(true);
try {
const branchList = await listRepositoryBranches(selectedRepo);
const branchResponse = await listRepositoryBranches(
fixedProjectId || selectedProject,
selectedRepo
);
const branchList = branchResponse.branches;
setBranches(branchList);
const defaultBranch = branchList.find((b) => b.is_default);
if (defaultBranch) {
@@ -94,7 +98,7 @@ export const CreateSessionForm = ({
}
};
void loadBranches();
}, [selectedRepo, showCloneMode]);
}, [selectedRepo, showCloneMode, selectedProject, fixedProjectId]);
// Filter repositories by selected project
const availableRepos = selectedProject
@@ -167,6 +171,32 @@ export const CreateSessionForm = ({
const isSubmitting = status === "creating";
// Determine which steps are active/unlocked
const hasProject = !!(fixedProjectId || selectedProject);
const hasRepo = !!(fixedRepoId || selectedRepo);
const hasToolType = !!selectedToolType;
const renderStep = (
label: string,
number: number,
isActive: boolean,
isComplete: boolean,
children: React.ReactNode
) => {
const stepClass = `workflow-step ${isActive ? "active" : ""} ${isComplete ? "complete" : ""}`;
return (
<div className={stepClass}>
<div className="workflow-step-header">
<span className="workflow-step-number">{number}</span>
<span className="workflow-step-label">{label}</span>
</div>
<div className="workflow-step-content">
{children}
</div>
</div>
);
};
return (
<div className={`create-session-form-wrapper ${className}`}>
{isSubmitting && (
@@ -178,11 +208,11 @@ export const CreateSessionForm = ({
</div>
)}
<form onSubmit={handleSubmit} className="stack create-session-form">
<div className="form-row">
{fixedProjectId && showFixedFields ? (
<form onSubmit={handleSubmit} className="stack create-session-form workflow-form">
{/* Step 1: Project */}
{renderStep("Select Project", 1, true, hasProject,
fixedProjectId && showFixedFields ? (
<label className="form-field">
Project
<input
type="text"
value={projectName || projects.find((p) => p.id === fixedProjectId)?.name || ""}
@@ -192,13 +222,15 @@ export const CreateSessionForm = ({
</label>
) : (
<label className="form-field">
Project
<select
value={selectedProject}
onChange={(e) => {
const value = e.target.value;
setSelectedProject(value);
setSelectedRepo("");
setSelectedToolType("");
setCloneMode("mount");
setIsCreatingNewBranch(false);
onProjectChange?.(value);
}}
disabled={isSubmitting}
@@ -211,11 +243,13 @@ export const CreateSessionForm = ({
))}
</select>
</label>
)}
)
)}
{fixedRepoId && showFixedFields ? (
{/* Step 2: Repository */}
{hasProject && renderStep("Select Repository", 2, true, hasRepo,
fixedRepoId && showFixedFields ? (
<label className="form-field">
Repository
<input
type="text"
value={repoName || repositories.find((r) => r.id === fixedRepoId)?.name || ""}
@@ -225,11 +259,15 @@ export const CreateSessionForm = ({
</label>
) : (
<label className="form-field">
Repository
<select
value={selectedRepo}
onChange={(e) => setSelectedRepo(e.target.value)}
disabled={!selectedProject || isSubmitting}
onChange={(e) => {
setSelectedRepo(e.target.value);
setSelectedToolType("");
setCloneMode("mount");
setIsCreatingNewBranch(false);
}}
disabled={!hasProject || isSubmitting}
>
<option value="">Select repository...</option>
{availableRepos.map((r) => (
@@ -239,14 +277,20 @@ export const CreateSessionForm = ({
))}
</select>
</label>
)}
)
)}
{/* Step 3: Tool Type */}
{hasRepo && renderStep("Select Tool", 3, true, hasToolType,
<label className="form-field">
Tool Type
<select
value={selectedToolType}
onChange={(e) => setSelectedToolType(e.target.value)}
disabled={isSubmitting}
onChange={(e) => {
setSelectedToolType(e.target.value);
setCloneMode("mount");
setIsCreatingNewBranch(false);
}}
disabled={!hasRepo || isSubmitting}
>
<option value="">Select tool...</option>
{toolTypes.map((t) => (
@@ -256,12 +300,12 @@ export const CreateSessionForm = ({
))}
</select>
</label>
</div>
)}
{showCloneMode && (
{/* Step 4: Clone Mode & Branch */}
{showCloneMode && hasToolType && renderStep("Repository Access", 4, true, false,
<div className="form-row">
<label className="form-field">
Repository Access
<div className="radio-group">
<label className="radio-label">
<input
@@ -269,7 +313,10 @@ export const CreateSessionForm = ({
name="cloneMode"
value="mount"
checked={cloneMode === "mount"}
onChange={(e) => setCloneMode(e.target.value as "mount" | "clone")}
onChange={(e) => {
setCloneMode(e.target.value as "mount" | "clone");
setIsCreatingNewBranch(false);
}}
disabled={isSubmitting}
/>
Mount (live sync)
@@ -280,7 +327,10 @@ export const CreateSessionForm = ({
name="cloneMode"
value="clone"
checked={cloneMode === "clone"}
onChange={(e) => setCloneMode(e.target.value as "mount" | "clone")}
onChange={(e) => {
setCloneMode(e.target.value as "mount" | "clone");
setIsCreatingNewBranch(false);
}}
disabled={isSubmitting}
/>
Clone fresh copy
@@ -376,48 +426,53 @@ export const CreateSessionForm = ({
</div>
)}
<label className="form-field">
Display Name (optional)
<input
type="text"
value={displayName}
onChange={(e) => setDisplayName(e.target.value)}
placeholder="My Development Environment"
disabled={isSubmitting}
/>
</label>
{/* Step 5: Display Name */}
{hasToolType && renderStep("Display Name (optional)", 5, true, !!displayName,
<label className="form-field">
<input
type="text"
value={displayName}
onChange={(e) => setDisplayName(e.target.value)}
placeholder="My Development Environment"
disabled={isSubmitting}
/>
</label>
)}
{/* Error & Submit */}
{error && <p className="error-text">{error}</p>}
<div className="form-actions">
{onCancel && (
{hasToolType && (
<div className="form-actions">
{onCancel && (
<button
className="secondary-button"
type="button"
onClick={onCancel}
disabled={isSubmitting}
>
Cancel
</button>
)}
<button
className="secondary-button"
type="button"
onClick={onCancel}
className="primary-button"
type="submit"
disabled={isSubmitting}
>
Cancel
{isSubmitting ? (
<>
<Icon name="loading" size="sm" />
Creating...
</>
) : (
<>
<Icon name="add" size="sm" />
{submitLabel}
</>
)}
</button>
)}
<button
className="primary-button"
type="submit"
disabled={isSubmitting}
>
{isSubmitting ? (
<>
<Icon name="loading" size="sm" />
Creating...
</>
) : (
<>
<Icon name="add" size="sm" />
{submitLabel}
</>
)}
</button>
</div>
</div>
)}
</form>
</div>
);
+59
View File
@@ -2844,6 +2844,65 @@ a.nav-item,
gap: var(--space-4);
}
/* Workflow Step Styles */
.workflow-form {
display: flex;
flex-direction: column;
gap: var(--space-6);
}
.workflow-step {
opacity: 0.4;
pointer-events: none;
transition: opacity 0.2s ease;
}
.workflow-step.active {
opacity: 1;
pointer-events: auto;
}
.workflow-step.complete {
opacity: 0.7;
pointer-events: auto;
}
.workflow-step-header {
display: flex;
align-items: center;
gap: var(--space-3);
margin-bottom: var(--space-3);
font-weight: 600;
color: var(--text-muted);
}
.workflow-step.active .workflow-step-header {
color: var(--text-primary);
}
.workflow-step-number {
display: flex;
align-items: center;
justify-content: center;
width: 28px;
height: 28px;
border-radius: 50%;
background: var(--bg-muted);
color: var(--text-muted);
font-size: 14px;
font-weight: 600;
}
.workflow-step.active .workflow-step-number {
background: var(--primary);
color: white;
}
.workflow-step.complete .workflow-step-number {
background: var(--success);
color: white;
}
.status-badge {
display: inline-flex;
align-items: center;
@@ -0,0 +1,415 @@
# Session Branch Selection Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Replace free-text branch input with a dropdown of available branches and add "Create new branch" functionality in the session creation form.
**Architecture:** Frontend fetches branches from existing API, displays them in a dropdown with a "Create new branch..." option. When creating a new branch, frontend sends both base branch and new branch name to backend. Backend clones the base branch then creates a local branch in the cloned workspace.
**Tech Stack:** React + TypeScript (frontend), FastAPI + Python (backend), Git via subprocess
---
## File Structure
- `apps/web/src/api/git_repositories.ts` — Add `listRepositoryBranches` API function
- `apps/web/src/pages/sessions.tsx` — Replace branch input with dropdown + new branch form
- `apps/api/src/api/tool_instances.py` — Extend `CreateInstanceRequest`, add local branch creation
- `apps/web/src/api/sessions.ts` — Update `createInstance` signature to accept `newBranch`
---
### Task 1: Add Branch Listing API to Frontend
**Files:**
- Modify: `apps/web/src/api/git_repositories.ts`
- [ ] **Step 1: Add Branch types and listRepositoryBranches function**
Add after the existing imports and before `export interface CommitHistoryEntry`:
```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;
}
```
- [ ] **Step 2: Commit**
```bash
git add apps/web/src/api/git_repositories.ts
git commit -m "feat: add branch listing API function"
```
---
### Task 2: Update Backend Request Model and Clone Logic
**Files:**
- Modify: `apps/api/src/api/tool_instances.py`
- [ ] **Step 1: Extend CreateInstanceRequest with new_branch field**
Change the `CreateInstanceRequest` class (around line 55-64):
```python
class CreateInstanceRequest(BaseModel):
"""Request body for creating a tool instance."""
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 for the instance")
clone_mode: str = Field(default="mount", description="Repository access mode: 'mount' or 'clone'")
branch: str | None = Field(default="main", description="Branch to clone (when clone_mode='clone')")
new_branch: str | None = Field(default=None, description="Create a new local branch after cloning")
```
- [ ] **Step 2: Add local branch creation after clone**
After the clone block (around line 248), add:
```python
# Create new local branch if requested
if data.clone_mode == "clone" and data.new_branch:
try:
result = subprocess.run(
["git", "-C", repo_path, "checkout", "-b", data.new_branch],
capture_output=True,
text=True,
)
if result.returncode != 0:
logger.error("Failed to create branch %s: %s", data.new_branch, result.stderr)
raise RuntimeError(f"Failed to create branch: {result.stderr}")
logger.info("Created local branch %s in cloned repository", data.new_branch)
except Exception as exc:
logger.exception("Failed to create local branch: %s", exc)
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=f"Failed to create local branch: {exc}"
)
```
- [ ] **Step 3: Update ToolInstance record to store new branch name**
Change the instance creation (around line 321):
```python
branch=data.new_branch if data.new_branch else (data.branch if data.clone_mode == "clone" else None),
```
- [ ] **Step 4: Commit**
```bash
git add apps/api/src/api/tool_instances.py
git commit -m "feat: support creating local branch during session creation
- Add new_branch field to CreateInstanceRequest
- Run git checkout -b after cloning when new_branch is provided
- Store new branch name in ToolInstance record"
```
---
### Task 3: Update Frontend API to Support newBranch
**Files:**
- Modify: `apps/web/src/api/sessions.ts`
- [ ] **Step 1: Update createInstance signature**
Change the function signature (around line 44):
```typescript
export async function createInstance(
projectId: string,
repoId: string,
toolTypeId: string,
displayName?: string,
cloneMode?: string,
branch?: string,
newBranch?: string
): Promise<ToolInstance> {
const response = await apiClient.post(
`/projects/${projectId}/repositories/${repoId}/instances`,
{
tool_type_id: toolTypeId,
display_name: displayName,
clone_mode: cloneMode || "mount",
branch: branch || undefined,
new_branch: newBranch || undefined,
}
);
return response.data;
}
```
- [ ] **Step 2: Commit**
```bash
git add apps/web/src/api/sessions.ts
git commit -m "feat: add newBranch parameter to createInstance"
```
---
### Task 4: Update Session Creation UI
**Files:**
- Modify: `apps/web/src/pages/sessions.tsx`
- [ ] **Step 1: Add new imports**
Add to existing imports:
```typescript
import { listRepositoryBranches, type Branch } from "../api/git_repositories";
```
- [ ] **Step 2: Add state variables**
Add after `const [sshKeys, setSshKeys] = useState<SSHKey[]>([]);` (around line 44):
```typescript
const [branches, setBranches] = useState<Branch[]>([]);
const [isLoadingBranches, setIsLoadingBranches] = useState(false);
const [isCreatingNewBranch, setIsCreatingNewBranch] = useState(false);
const [newBranchName, setNewBranchName] = useState("");
const [baseBranch, setBaseBranch] = useState("");
```
- [ ] **Step 3: Add branch loading effect**
Add after the `loadSshKeys` effect (around line 117):
```typescript
useEffect(() => {
const loadBranches = async () => {
if (!selectedRepo || !selectedProject || cloneMode !== "clone") {
setBranches([]);
setIsCreatingNewBranch(false);
setNewBranchName("");
setBaseBranch("");
return;
}
setIsLoadingBranches(true);
try {
const data = await listRepositoryBranches(selectedProject, selectedRepo);
setBranches(data.branches);
const defaultBranch = data.default_branch;
setBaseBranch(defaultBranch);
if (!branch || !data.branches.find((b) => b.name === branch)) {
setBranch(defaultBranch);
}
} catch {
setBranches([]);
} finally {
setIsLoadingBranches(false);
}
};
void loadBranches();
}, [selectedRepo, selectedProject, cloneMode]);
```
- [ ] **Step 4: Replace branch input with dropdown**
Replace the branch input section (around lines 686-696):
```tsx
{cloneMode === "clone" && (
<>
<label className="form-field">
Branch
{isLoadingBranches ? (
<span className="muted">Loading branches...</span>
) : (
<select
value={isCreatingNewBranch ? "__new__" : branch}
onChange={(e) => {
const value = e.target.value;
if (value === "__new__") {
setIsCreatingNewBranch(true);
setNewBranchName("");
} else {
setIsCreatingNewBranch(false);
setBranch(value);
setBaseBranch(value);
}
}}
>
{branches.map((b) => (
<option key={b.name} value={b.name}>
{b.name} {b.is_default ? "(default)" : ""}
</option>
))}
<option value="__new__">Create new branch...</option>
</select>
)}
</label>
{isCreatingNewBranch && (
<>
<label className="form-field">
New Branch Name
<input
type="text"
value={newBranchName}
onChange={(e) => setNewBranchName(e.target.value)}
placeholder="feature/my-new-branch"
required
/>
</label>
<label className="form-field">
Base Branch
<select
value={baseBranch}
onChange={(e) => setBaseBranch(e.target.value)}
>
{branches.map((b) => (
<option key={b.name} value={b.name}>
{b.name} {b.is_default ? "(default)" : ""}
</option>
))}
</select>
</label>
</>
)}
{selectedRepo && (
<div className="form-field ssh-key-info">
{(() => {
const repo = repositories.find((r) => r.id === selectedRepo);
if (!repo) return null;
if (repo.ssh_key_id) {
const key = sshKeys.find((k) => k.id === repo.ssh_key_id);
return (
<span className="success-text">
SSH key: {key?.name || "Assigned"}
</span>
);
}
return (
<span className="warning-text">
No SSH key assigned to this repository. Clone mode requires an SSH key.
</span>
);
})()}
</div>
)}
</>
)}
```
- [ ] **Step 5: Update handleCreate to pass newBranch**
Change the `createInstance` call (around line 210):
```typescript
const instance = await createInstance(
selectedProject,
selectedRepo,
selectedToolType,
displayName || undefined,
cloneMode,
isCreatingNewBranch ? baseBranch : branch,
isCreatingNewBranch ? newBranchName : undefined
);
```
- [ ] **Step 6: Reset new branch state on successful creation**
In the success handler (around line 227), add:
```typescript
setIsCreatingNewBranch(false);
setNewBranchName("");
setBaseBranch("");
setBranches([]);
```
- [ ] **Step 7: Commit**
```bash
git add apps/web/src/pages/sessions.tsx
git commit -m "feat: add branch dropdown and new branch creation UI
- Replace free-text branch input with dropdown of available branches
- Add 'Create new branch...' option with name and base branch inputs
- Load branches from API when repository is selected in clone mode
- Pass newBranch parameter to createInstance API"
```
---
### Task 5: Verify and Test
- [ ] **Step 1: Check TypeScript compilation**
```bash
cd apps/web && npm run typecheck
```
Expected: No errors
- [ ] **Step 2: Check Python syntax**
```bash
cd apps/api && python -m py_compile src/api/tool_instances.py
```
Expected: No errors
- [ ] **Step 3: Run backend tests if available**
```bash
cd apps/api && pytest src/tests/ -v -k "instance" || echo "No instance tests found"
```
- [ ] **Step 4: Commit**
```bash
git add -A
git commit -m "test: verify branch selection implementation compiles"
```
---
## Spec Coverage Check
| Spec Requirement | Task |
|---|---|
| Branch dropdown with available branches | Task 4 |
| "Create new branch..." option | Task 4 |
| New branch name input | Task 4 |
| Base branch dropdown | Task 4 |
| Backend clone + local branch creation | Task 2 |
| Frontend API integration | Task 1, 3 |
## Placeholder Scan
- No TBD, TODO, or "implement later" references
- All code is complete and copy-paste ready
- No vague instructions like "add appropriate error handling"
## Type Consistency Check
- `Branch` interface used consistently in Task 1 and Task 4
- `new_branch` / `newBranch` naming consistent between frontend and backend
- `CreateInstanceRequest` fields match API call in `sessions.ts`