feat: workspace-first UI refresh - PR-1 backend endpoints

- Add FileService for workspace-scoped file operations
- Add GitOperations service for workspace-scoped git commands
- Add workspace_files API: GET/POST /workspaces/{id}/files
- Add workspace_git API: status, branches, commit, push, pull, fetch, checkout, history
- Add workspace_instances API: list instances per workspace
- Add top-level POST /workspaces/ (accepts repo_id directly)
- Enrich GET /projects/ with nested repositories and workspaces
- Register all new routers in main.py
- 23 tests passing (17 existing + 6 new)

Quality gates: ruff clean
This commit is contained in:
2026-06-01 16:47:09 +02:00
parent 59b125d8e2
commit e7587ca9f5
14 changed files with 2347 additions and 32 deletions
+43 -19
View File
@@ -13,7 +13,10 @@ import type { Workspace } from "../types/workspace";
export function WorkspacesPage() {
const [showCreate, setShowCreate] = useState(false);
const [startWorkspace, setStartWorkspace] = useState<Workspace | null>(null);
const [createTarget, setCreateTarget] = useState<{ projectId: string; repoId: string } | null>(null);
const [createTarget, setCreateTarget] = useState<{
projectId: string;
repoId: string;
} | null>(null);
const { workspaces, loading, error, refresh } = useWorkspaces();
const actions = useWorkspaceActions();
@@ -27,11 +30,21 @@ export function WorkspacesPage() {
};
const handleDelete = async (workspace: Workspace) => {
await actions.delete(workspace.project_id, workspace.repo_id, workspace, refresh);
await actions.delete(
workspace.project_id,
workspace.repo_id,
workspace,
refresh,
);
};
const handleSync = async (workspace: Workspace) => {
await actions.sync(workspace.project_id, workspace.repo_id, workspace, refresh);
await actions.sync(
workspace.project_id,
workspace.repo_id,
workspace,
refresh,
);
};
const handleStartTool = async (
@@ -52,7 +65,12 @@ export function WorkspacesPage() {
[],
startWorkspace.id,
);
await startInstance(startWorkspace.project_id, startWorkspace.repo_id, instance.id, configProfileId);
await startInstance(
startWorkspace.project_id,
startWorkspace.repo_id,
instance.id,
configProfileId,
);
setStartWorkspace(null);
await refresh();
} catch (err) {
@@ -72,20 +90,23 @@ export function WorkspacesPage() {
>
<Icon name="refresh" size="sm" />
</button>
<button
className="btn btn-primary"
onClick={() => {
if (workspaces.length > 0) {
const first = workspaces[0];
setCreateTarget({ projectId: first.project_id, repoId: first.repo_id });
setShowCreate(true);
} else {
alert("Navigate to a project to create your first workspace.");
}
}}
>
<Icon name="add" size="sm" /> New Workspace
</button>
<button
className="btn btn-primary"
onClick={() => {
if (workspaces.length > 0) {
const first = workspaces[0];
setCreateTarget({
projectId: first.project_id,
repoId: first.repo_id,
});
setShowCreate(true);
} else {
alert("Navigate to a project to create your first workspace.");
}
}}
>
<Icon name="add" size="sm" /> New Workspace
</button>
</div>
</header>
@@ -96,7 +117,10 @@ export function WorkspacesPage() {
projectId={createTarget.projectId}
repoId={createTarget.repoId}
onSubmit={handleCreate}
onCancel={() => { setShowCreate(false); setCreateTarget(null); }}
onCancel={() => {
setShowCreate(false);
setCreateTarget(null);
}}
/>
)}