feat(web/ui): rework project pane into flat list with repo/workspace/tool hierarchy

- Add ProjectListItem component with project header actions
- Show repositories horizontally with branch labels
- List workspaces vertically under each repo
- Show running tools per workspace from useSessions
- Link workspace names to /workspaces/:id
- Link tool names to web URL or terminal page
- Keep existing mobile view and dialogs unchanged
This commit is contained in:
Developer
2026-06-16 21:20:04 +00:00
parent 39bac24100
commit 563ff13969
6 changed files with 958 additions and 34 deletions
+5 -34
View File
@@ -7,11 +7,13 @@ import {
import { Icon } from "../components/icon";
import { useMobileViewport } from "../hooks/use-mobile-viewport";
import { ProjectCard } from "../components/features/project/ProjectCard";
import { ProjectListItem } from "../components/features/project/ProjectListItem";
import { ProjectDialog } from "../components/features/project/ProjectDialog";
import { RepositoryCreateDialog } from "../components/features/project/repository-create-dialog";
import { MobileListView } from "../components/features/mobile/mobile-list-view";
import { MobileFAB } from "../components/features/mobile/mobile-fab";
import { useProjects } from "../hooks/use-projects";
import { useSessions } from "../state/sessions";
import type { ProjectWithRepos } from "../types";
type MobileView = "list" | "detail" | "create-project" | "create-repo";
@@ -34,8 +36,6 @@ export const ProjectsPage = () => {
formError,
deleteConfirmId,
setDeleteConfirmId,
expandedProject,
setExpandedProject,
creatingWorkspace,
setCreatingWorkspace,
workspaceLoading,
@@ -48,6 +48,7 @@ export const ProjectsPage = () => {
handleDeleteWorkspace,
} = useProjects();
const { sessions } = useSessions();
const isEmpty = status === "ready" && projects.length === 0;
/* ── Mobile views ── */
@@ -242,45 +243,15 @@ export const ProjectsPage = () => {
{status === "ready" && projects.length > 0 && (
<div className="project-list">
{projects.map((project) => (
<ProjectCard
<ProjectListItem
key={project.id}
project={project}
expanded={expandedProject === project.id}
sessions={sessions}
deleteConfirm={deleteConfirmId === project.id}
workspaceLoading={workspaceLoading}
showCreateForm={
creatingWorkspace?.projectId === project.id
? creatingWorkspace.repoId
: null
}
onToggle={() =>
setExpandedProject(
expandedProject === project.id ? null : project.id,
)
}
onEdit={() => openEdit(project)}
onDelete={() => setDeleteConfirmId(project.id)}
onConfirmDelete={() => void handleDelete(project.id)}
onCancelDelete={() => setDeleteConfirmId(null)}
onCreateWorkspace={(repoId) =>
setCreatingWorkspace({ projectId: project.id, repoId })
}
onWorkspaceAction={(repoId, workspace, action) => {
if (action === "sync") {
void handleSyncWorkspace(project.id, repoId, workspace);
} else if (action === "delete") {
void handleDeleteWorkspace(workspace);
}
}}
onCancelCreate={() => setCreatingWorkspace(null)}
onAddRepository={() => {
setSelectedProject(project);
setMobileView("create-repo");
}}
onCreated={() => {
setCreatingWorkspace(null);
reload();
}}
/>
))}
</div>