feat: workspace-first UI refresh - PR-2 workspace detail page

- Add workspace detail page (/workspaces/:id) with 4 tabs:
  - Files: file tree, viewer, editor, git toolbar (commit/push/pull/fetch)
  - Git: branch selector, commit history
  - Tools: instance grid, start tool modal
  - Settings: workspace info read-only
- Add workspace API clients: workspace-files, workspace-git, workspace-instances
- Add hooks: useWorkspaceFiles, useWorkspaceGit, useWorkspaceInstances
- WorkspaceCard links to detail page via router Link
- Add comprehensive CSS for workspace detail layout
- Mobile: bottom tab bar, responsive file tree/split
- TypeScript + eslint clean

Quality gates: tsc --noEmit clean, eslint clean
This commit is contained in:
2026-06-01 17:04:44 +02:00
parent e7587ca9f5
commit 27c77af591
12 changed files with 1366 additions and 33 deletions
+45
View File
@@ -0,0 +1,45 @@
/** Workspace file API client. */
import { apiClient } from "./client";
export interface FileEntry {
name: string;
path: string;
type: "file" | "directory";
size?: number;
}
export async function listWorkspaceFiles(
workspaceId: string,
path: string = "",
): Promise<FileEntry[]> {
const response = await apiClient.get<{ entries: FileEntry[] }>(
`/workspaces/${workspaceId}/files/`,
{ params: { path } },
);
return response.data.entries;
}
export async function getWorkspaceFileContent(
workspaceId: string,
path: string,
): Promise<string> {
const response = await apiClient.get<{ content: string }>(
`/workspaces/${workspaceId}/files/content`,
{ params: { path } },
);
return response.data.content;
}
export async function saveWorkspaceFile(
workspaceId: string,
path: string,
content: string,
commitMessage?: string,
): Promise<void> {
await apiClient.post(`/workspaces/${workspaceId}/files/content`, {
path,
content,
message: commitMessage,
});
}
+75
View File
@@ -0,0 +1,75 @@
/** Workspace git API client. */
import { apiClient } from "./client";
export interface GitStatus {
branch: string;
modified: string[];
added: string[];
deleted: string[];
untracked: string[];
ahead: number;
behind: number;
}
export interface Commit {
hash: string;
message: string;
author: string;
date: string;
}
export async function getGitStatus(workspaceId: string): Promise<GitStatus> {
const response = await apiClient.get<GitStatus>(
`/workspaces/${workspaceId}/git/status`,
);
return response.data;
}
export async function getGitBranches(
workspaceId: string,
): Promise<{ branches: string[]; current_branch: string }> {
const response = await apiClient.get<{
branches: string[];
current_branch: string;
}>(`/workspaces/${workspaceId}/git/branches`);
return response.data;
}
export async function gitCommit(
workspaceId: string,
message: string,
): Promise<void> {
await apiClient.post(`/workspaces/${workspaceId}/git/commit`, { message });
}
export async function gitPush(workspaceId: string): Promise<void> {
await apiClient.post(`/workspaces/${workspaceId}/git/push`);
}
export async function gitPull(workspaceId: string): Promise<void> {
await apiClient.post(`/workspaces/${workspaceId}/git/pull`);
}
export async function gitFetch(workspaceId: string): Promise<void> {
await apiClient.post(`/workspaces/${workspaceId}/git/fetch`);
}
export async function gitCheckout(
workspaceId: string,
branch: string,
): Promise<void> {
await apiClient.post(`/workspaces/${workspaceId}/git/checkout`, { branch });
}
export async function getGitHistory(
workspaceId: string,
path?: string,
limit: number = 50,
): Promise<Commit[]> {
const response = await apiClient.get<{ commits: Commit[] }>(
`/workspaces/${workspaceId}/git/history`,
{ params: { path, limit } },
);
return response.data.commits;
}
+30
View File
@@ -0,0 +1,30 @@
/** Workspace instance API client. */
import { apiClient } from "./client";
import type { ToolInstance } from "./sessions";
export async function listWorkspaceInstances(
workspaceId: string,
): Promise<ToolInstance[]> {
const response = await apiClient.get<ToolInstance[]>(
`/workspaces/${workspaceId}/instances/`,
);
return response.data;
}
export async function createWorkspaceInstance(
workspaceId: string,
toolTypeId: string,
displayName?: string,
configProfileId?: string,
): Promise<ToolInstance> {
const response = await apiClient.post<ToolInstance>(
`/workspaces/${workspaceId}/instances/`,
{
tool_type_id: toolTypeId,
display_name: displayName,
config_profile_id: configProfileId,
},
);
return response.data;
}