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
This commit is contained in:
2026-05-24 09:25:48 +00:00
parent 0d57e3501a
commit d7fb51f427
+93 -8
View File
@@ -3,7 +3,7 @@ import { useNavigate } from "react-router-dom";
import { listProjects } from "../api/projects";
import type { Project } from "../types";
import { listRepositories, type GitRepository } from "../api/git_repositories";
import { listRepositories, listRepositoryBranches, type GitRepository, type Branch } from "../api/git_repositories";
import {
getUserSessions,
type Session,
@@ -43,6 +43,12 @@ export const SessionsPage = () => {
const [branch, setBranch] = useState("main");
const [sshKeys, setSshKeys] = useState<SSHKey[]>([]);
const [branches, setBranches] = useState<Branch[]>([]);
const [isLoadingBranches, setIsLoadingBranches] = useState(false);
const [isCreatingNewBranch, setIsCreatingNewBranch] = useState(false);
const [newBranchName, setNewBranchName] = useState("");
const [baseBranch, setBaseBranch] = useState("");
const [dirtyDeleteSession, setDirtyDeleteSession] = useState<Session | null>(null);
const [dirtyDeleteFiles, setDirtyDeleteFiles] = useState<string[]>([]);
@@ -116,6 +122,33 @@ export const SessionsPage = () => {
void loadSshKeys();
}, []);
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]);
// Poll health every 30 seconds for active instances
useEffect(() => {
const checkHealth = async () => {
@@ -213,7 +246,8 @@ export const SessionsPage = () => {
selectedToolType,
displayName || undefined,
cloneMode,
cloneMode === "clone" ? branch : undefined
isCreatingNewBranch ? baseBranch : branch,
isCreatingNewBranch ? newBranchName : undefined
);
// Auto-start the instance
@@ -227,6 +261,10 @@ export const SessionsPage = () => {
setDisplayName("");
setCloneMode("mount");
setBranch("main");
setIsCreatingNewBranch(false);
setNewBranchName("");
setBaseBranch("");
setBranches([]);
await loadSessions();
} catch (error) {
setCreateStatus("error");
@@ -687,14 +725,61 @@ export const SessionsPage = () => {
<>
<label className="form-field">
Branch
<input
type="text"
value={branch}
onChange={(e) => setBranch(e.target.value)}
placeholder="main"
/>
{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">
{(() => {