Files
headquarter/docs/superpowers/plans/2026-05-24-session-branch-selection.md
T
alex 96c8dd7402 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
2026-05-24 10:14:39 +00:00

13 KiB

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:

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
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):

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:

        # 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):

            branch=data.new_branch if data.new_branch else (data.branch if data.clone_mode == "clone" else None),
  • Step 4: Commit
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):

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

import { listRepositoryBranches, type Branch } from "../api/git_repositories";
  • Step 2: Add state variables

Add after const [sshKeys, setSshKeys] = useState<SSHKey[]>([]); (around line 44):

  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):

  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):

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

      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:

      setIsCreatingNewBranch(false);
      setNewBranchName("");
      setBaseBranch("");
      setBranches([]);
  • Step 7: Commit
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
cd apps/web && npm run typecheck

Expected: No errors

  • Step 2: Check Python syntax
cd apps/api && python -m py_compile src/api/tool_instances.py

Expected: No errors

  • Step 3: Run backend tests if available
cd apps/api && pytest src/tests/ -v -k "instance" || echo "No instance tests found"
  • Step 4: Commit
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