feat: complete working-copies workspace-first cleanup
- Remove clone_mode/branch/new_branch from frontend create session flow. - Add workspace picker to CreateSessionForm; auto-create default workspace when repo selected. - Fix tool-starter.tsx and use-start-tool.ts createInstance signatures after API change. - Remove clone mode badge from SessionCard. - Delete stale backend unit tests referencing removed clone_mode schema fields. - Update OpenSpec working-copies tasks and mark change completed. - Regenerate project maps. Quality gates: npm run typecheck, npm run lint, npm test -- --run (82 passed), python3 -m py_compile on changed backend files.
This commit is contained in:
@@ -13,6 +13,8 @@ import {
|
||||
listConfigProfiles,
|
||||
type ConfigProfile,
|
||||
} from "../../../api/config-profiles";
|
||||
import { listWorkspaces, createWorkspace } from "../../../api/workspaces";
|
||||
import type { Workspace } from "../../../types/workspace";
|
||||
|
||||
interface CreateSessionFormProps {
|
||||
projects: Project[];
|
||||
@@ -54,6 +56,10 @@ export const CreateSessionForm = ({
|
||||
const [selectedConfigProfile, setSelectedConfigProfile] = useState("");
|
||||
const [selectedSshKeyIds, setSelectedSshKeyIds] = useState<string[]>([]);
|
||||
|
||||
const [workspaces, setWorkspaces] = useState<Workspace[]>([]);
|
||||
const [selectedWorkspaceId, setSelectedWorkspaceId] = useState("");
|
||||
const [isLoadingWorkspaces, setIsLoadingWorkspaces] = useState(false);
|
||||
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
@@ -94,6 +100,50 @@ export const CreateSessionForm = ({
|
||||
void loadProfiles();
|
||||
}, [selectedToolType, selectedProject, fixedProjectId]);
|
||||
|
||||
// Load workspaces when repository is selected
|
||||
useEffect(() => {
|
||||
const projectId = fixedProjectId || selectedProject;
|
||||
const repoId = fixedRepoId || selectedRepo;
|
||||
if (!projectId || !repoId) {
|
||||
setWorkspaces([]);
|
||||
setSelectedWorkspaceId("");
|
||||
return;
|
||||
}
|
||||
const loadWorkspaces = async () => {
|
||||
setIsLoadingWorkspaces(true);
|
||||
try {
|
||||
const data = await listWorkspaces(projectId, repoId);
|
||||
setWorkspaces(data);
|
||||
if (data.length > 0) {
|
||||
setSelectedWorkspaceId(data[0].id);
|
||||
} else {
|
||||
// Auto-create a default workspace so the user can start a tool
|
||||
const workspace = await createWorkspace(projectId, repoId, {
|
||||
name: "default",
|
||||
branch: "main",
|
||||
});
|
||||
setWorkspaces([workspace]);
|
||||
setSelectedWorkspaceId(workspace.id);
|
||||
}
|
||||
} catch (err) {
|
||||
setError(
|
||||
err instanceof Error ? err.message : "Failed to load workspaces",
|
||||
);
|
||||
setWorkspaces([]);
|
||||
setSelectedWorkspaceId("");
|
||||
} finally {
|
||||
setIsLoadingWorkspaces(false);
|
||||
}
|
||||
};
|
||||
void loadWorkspaces();
|
||||
}, [
|
||||
fixedProjectId,
|
||||
selectedProject,
|
||||
fixedRepoId,
|
||||
selectedRepo,
|
||||
repositories,
|
||||
]);
|
||||
|
||||
// Filter repositories by selected project
|
||||
const availableRepos = selectedProject
|
||||
? repositories.filter((r) => r.project_id === selectedProject)
|
||||
@@ -106,6 +156,8 @@ export const CreateSessionForm = ({
|
||||
setDisplayName("");
|
||||
setSelectedSshKeyIds([]);
|
||||
setSelectedConfigProfile("");
|
||||
setWorkspaces([]);
|
||||
setSelectedWorkspaceId("");
|
||||
};
|
||||
|
||||
const handleSubmit = async (event: React.FormEvent) => {
|
||||
@@ -123,12 +175,19 @@ export const CreateSessionForm = ({
|
||||
setIsSubmitting(true);
|
||||
|
||||
try {
|
||||
const workspaceId = selectedWorkspaceId || undefined;
|
||||
if (!workspaceId) {
|
||||
setError("No workspace available for the selected repository");
|
||||
setIsSubmitting(false);
|
||||
return;
|
||||
}
|
||||
|
||||
const instance = await createInstance(
|
||||
projectId,
|
||||
repoId,
|
||||
selectedToolType,
|
||||
displayName || undefined,
|
||||
undefined,
|
||||
workspaceId,
|
||||
selectedConfigProfile || undefined,
|
||||
selectedSshKeyIds.length > 0 ? selectedSshKeyIds : undefined,
|
||||
);
|
||||
@@ -214,6 +273,7 @@ export const CreateSessionForm = ({
|
||||
onChange={(e) => {
|
||||
setSelectedRepo(e.target.value);
|
||||
setSelectedToolType("");
|
||||
setSelectedWorkspaceId("");
|
||||
}}
|
||||
disabled={!hasProject || isSubmitting}
|
||||
>
|
||||
@@ -228,6 +288,30 @@ export const CreateSessionForm = ({
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Workspace */}
|
||||
{hasRepo && (
|
||||
<div className="form-field">
|
||||
<label>Workspace</label>
|
||||
{isLoadingWorkspaces ? (
|
||||
<span className="muted">Loading workspaces...</span>
|
||||
) : workspaces.length === 0 ? (
|
||||
<span className="muted">No workspace available</span>
|
||||
) : (
|
||||
<select
|
||||
value={selectedWorkspaceId}
|
||||
onChange={(e) => setSelectedWorkspaceId(e.target.value)}
|
||||
disabled={!hasRepo || isSubmitting || isLoadingWorkspaces}
|
||||
>
|
||||
{workspaces.map((w) => (
|
||||
<option key={w.id} value={w.id}>
|
||||
{w.name} ({w.branch})
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Tool Type */}
|
||||
{hasRepo && (
|
||||
<div className="form-field">
|
||||
|
||||
Reference in New Issue
Block a user