feat: complete workspace-first-ui cleanup and tests
- Delete dead repo-workspace code: RepoWorkspacePage, useRepoWorkspace, WorkspaceLayout, FileBrowser, old git components (git-toolbar, file-editor, commit-panel), and repo-workspace.css. - Fix stale backend test imports for moved models/services. - Add GitOperations unit tests. - Add integration tests for workspace files, git, and instances endpoints. - Add frontend tests for WorkspaceDetailPage and ProjectCard. - Update OpenSpec workspace-first-ui tasks and mark change completed. - Regenerate project maps. Quality gates: npm run typecheck, npm run lint, npm test -- --run (87 passed), python3 -m py_compile on changed backend files, pytest backend workspace tests.
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
dir: apps/web/src/pages
|
||||
|
||||
## role
|
||||
Implements the complete set of top-level routed page components for the web application, covering all major user-facing features including project management, git operations, workspace management, terminal access, user settings, and system administration.
|
||||
Contains all top-level page components that serve as route endpoints for the web application's main navigation areas, each handling a distinct functional domain of the development environment platform.
|
||||
## parent
|
||||
index: apps/web/src/.pi-map.index.md
|
||||
map: apps/web/src/.pi-map.md
|
||||
@@ -19,12 +19,12 @@ map: apps/web/src/.pi-map.md
|
||||
- ProjectSettingsPage.tsx
|
||||
- ProjectsPage.test.tsx
|
||||
- ProjectsPage.tsx
|
||||
- RepoWorkspacePage.tsx
|
||||
- SessionsPage.tsx
|
||||
- SettingsPage.tsx
|
||||
- SshKeysPage.tsx
|
||||
- TerminalPage.tsx
|
||||
- ToolWorkshopPage.tsx
|
||||
- WorkspaceDetailPage.test.tsx
|
||||
- WorkspaceDetailPage.tsx
|
||||
- WorkspacesPage.tsx
|
||||
## links
|
||||
@@ -34,6 +34,6 @@ map: apps/web/src/pages/.pi-map.md
|
||||
- change pages behavior
|
||||
read: ConfigProfilesPage.tsx, DashboardPage.tsx, GitHistoryPage.tsx
|
||||
- update pages tests
|
||||
read: DashboardPage.test.tsx, ProjectsPage.test.tsx
|
||||
read: DashboardPage.test.tsx, ProjectsPage.test.tsx, WorkspaceDetailPage.test.tsx
|
||||
## dirty
|
||||
-
|
||||
|
||||
@@ -4,10 +4,10 @@ dir: apps/web/src/pages
|
||||
index: apps/web/src/pages/.pi-map.index.md
|
||||
|
||||
## role
|
||||
Implements the complete set of top-level routed page components for the web application, covering all major user-facing features including project management, git operations, workspace management, terminal access, user settings, and system administration.
|
||||
Contains all top-level page components that serve as route endpoints for the web application's main navigation areas, each handling a distinct functional domain of the development environment platform.
|
||||
## files
|
||||
- ConfigProfilesPage.tsx | Renders a responsive configuration profiles management page with sidebar list and editor panel for desktop, and a dedicated mobile view for creating, editing, and managing config profiles. | exp: ConfigProfilesPage | dep: react, ../components/data-states, ../hooks/use-mobile-viewport, ../hooks/use-config-profiles, ../components/features/config-profiles/ConfigProfileListSidebar, ../components/features/config-profiles/ConfigProfileEditorPanel, ../components/features/config-profiles/ConfigProfilesMobileView
|
||||
- DashboardPage.test.tsx | Tests the DashboardPage (HomePage) component's rendering, loading states, and error handling with retry functionality. | dep: @testing-library/react, react-router-dom, vitest, ./DashboardPage, ../state/sessions, ../state/session-operations, ../api/dashboard, ../api/sessions, ../api/projects, ../api/git-repositories, ../api/tool-types
|
||||
- DashboardPage.test.tsx | Unit tests for the DashboardPage/ HomePage component verifying overview loading and error retry behavior. | dep: @testing-library/jest-dom/vitest, @testing-library/react, react-router-dom, vitest, ./DashboardPage, ../state/sessions, ../state/session-operations, DashboardPage, SessionsProvider, SessionOperationsProvider
|
||||
- DashboardPage.tsx | Renders a dashboard homepage that displays workspace overview, active/recent sessions, summary statistics, and polling health checks for running instances. | exp: HomePage | dep: react, react-router-dom, ../api/dashboard, ../api/sessions, ../components/data-states, ../components/features/session/session-list, ../hooks/use-instance-actions, ../state/sessions
|
||||
- GitHistoryPage.tsx | Renders a Git commit history page with branch selection, commit list with graph visualization, and a detail panel showing commit metadata, stats, and diffs. | exp: GitHistoryPage | dep: react, react-router-dom, ../api/git-repositories, ../components/data-states, ../components/icon, ../hooks/use-async-data
|
||||
- GitRepositoriesPage.tsx | Displays and manages a project's Git repositories with CRUD operations including listing, creating, navigating to history, and deleting with confirmation | exp: GitRepositoriesPage | dep: react, react-router-dom, ../api/git-repositories, ../components/data-states, ../components/icon, ../components/features/project/repository-create-dialog, ../hooks/use-async-data
|
||||
@@ -16,18 +16,18 @@ Implements the complete set of top-level routed page components for the web appl
|
||||
- ProjectSettingsPage.tsx | Renders a project settings page with tabbed navigation for general settings, repositories, and members, including project data fetching, editing, and deletion capabilities. | exp: ProjectSettingsPage | dep: react, react-router-dom, ../components/features/settings/settings-tab-layout, ../components/features/project/repositories-settings-tab, ../api/client, ../types
|
||||
- ProjectsPage.test.tsx | Unit tests for the ProjectsPage component covering loading, empty, error, create, edit, and delete states with API mocking. | dep: @testing-library/react, react-router-dom, vitest, ./ProjectsPage, ../api/projects, ProjectsPage
|
||||
- ProjectsPage.tsx | Renders a responsive projects management page with separate mobile and desktop layouts, supporting project CRUD operations, repository creation, and workspace management. | exp: ProjectsPage | dep: react, ../components/data-states, ../components/icon, ../hooks/use-mobile-viewport, ../components/features/project/ProjectCard, ../components/features/project/ProjectDialog, ../components/features/project/repository-create-dialog, ../components/features/mobile/mobile-list-view, ../components/features/mobile/mobile-fab, ../hooks/use-projects, ../types
|
||||
- RepoWorkspacePage.tsx | Renders a repository workspace page with loading/error/empty states and a responsive layout for managing repositories, branches, and git operations. | exp: RepoWorkspace | dep: react, react-router-dom, ../components/data-states, ../hooks/use-mobile-viewport, ../hooks/use-repo-workspace, ../components/features/workspace/workspace-header, ../components/features/workspace/WorkspaceLayout
|
||||
- SessionsPage.tsx | Renders a sessions management page that displays, polls health for, and handles CRUD operations on development environment sessions with dirty delete confirmation. | exp: SessionsPage | dep: react, ../api/sessions, ../api/settings, ../components/data-states, ../components/features/session/session-list, ../components/features/session/session-card, ../hooks/use-instance-actions, ../state/sessions
|
||||
- SettingsPage.tsx | A React settings page component that loads, displays, and manages user configuration with tabbed navigation and nested outlet for child routes. | exp: SettingsPage | dep: react, react-router-dom, ../api/settings, ../components/data-states, ../hooks/use-async-data, ../components/features/settings/GeneralSettingsTab
|
||||
- SshKeysPage.tsx | React page component for managing SSH keys including generation, listing, signing, verification, and deletion | exp: SSHKeysPage | dep: react-router-dom, ../components/data-states, ../hooks/use-ssh-keys, ../components/features/ssh-keys/SSHKeyCreateForm, ../components/features/ssh-keys/SSHKeyList
|
||||
- TerminalPage.tsx | Renders a responsive terminal page that switches between mobile and desktop views based on device type, managing terminal sessions and their interactions. | exp: TerminalPage | dep: react, ../hooks/use-terminal-page, ../components/features/terminal/MobileTerminalView, ../components/features/terminal/DesktopTerminalView, useTerminalPage hook, MobileTerminalView, DesktopTerminalView
|
||||
- ToolWorkshopPage.tsx | Renders a responsive tool workshop page with sidebar/editor layout for desktop and tabbed mobile view for managing tool types | exp: ToolWorkshopPage | dep: ../components/data-states, ../hooks/use-mobile-viewport, ../hooks/use-tool-workshop, ../components/features/tool-workshop/ToolTypeListSidebar, ../components/features/tool-workshop/ToolTypeEditorPanel, ../components/features/tool-workshop/ToolWorkshopMobileView, react, use-mobile-viewport, use-tool-workshop, data-states, ToolTypeListSidebar, ToolTypeEditorPanel, ToolWorkshopMobileView
|
||||
- WorkspaceDetailPage.test.tsx | Tests the WorkspaceDetailPage component rendering workspace headers, tabs, and tab switching behavior | dep: @testing-library/jest-dom/vitest, @testing-library/react, react-router-dom, vitest, ./WorkspaceDetailPage, @testing-library/jest-dom, WorkspaceDetailPage, use-workspaces, use-workspace-files, use-workspace-git, use-workspace-instances, use-mobile-viewport
|
||||
- WorkspaceDetailPage.tsx | Renders a workspace detail page with tabbed navigation for files, git, tools, and settings management. | exp: func:WorkspaceDetailPage(), call:useParams, call:useState, call:useMobileViewport, call:useWorkspaces, call:workspaces.find | dep: react, react-router-dom, ../components/icon, ../hooks/use-workspaces, ../hooks/use-workspace-files, ../hooks/use-workspace-git, ../hooks/use-workspace-instances, ../hooks/use-mobile-viewport, ../components/features/tool/tool-starter, ../api/workspace-files, ../types/workspace
|
||||
- WorkspacesPage.tsx | Renders a responsive workspaces management page with separate mobile (list/detail/create views) and desktop (grid with cards) layouts, supporting CRUD operations and tool launching. | exp: func:WorkspacesPage(), call:useMobileViewport, call:useState, call:useWorkspaces, call:useWorkspaceActions, call:actions.delete, call:actions.sync, call:setMobileView, call:refresh, call:setStartWorkspace, call:handleDelete, call:setSelectedWorkspace, call:workspaces.map, call:workspaces.find, call:e.stopPropagation, call:setShowCreate | dep: react, ../components/icon, ../hooks/use-mobile-viewport, ../hooks/use-workspaces, ../hooks/use-workspace-actions, ../components/features/workspace/workspace-card, ../components/features/workspace/workspace-create-form, ../components/features/mobile/mobile-list-view, ../components/features/mobile/mobile-detail-view, ../components/features/mobile/mobile-fab, ../components/features/tool/tool-starter, ../types/workspace
|
||||
## arch
|
||||
Follows a page-based routing architecture where each file corresponds to a route endpoint, using responsive conditional rendering patterns (mobile/desktop split layouts), tabbed navigation with nested outlets, polling-based real-time data updates, and container-presentational component separation with embedded data fetching, state management, and CRUD operations directly in page components.
|
||||
Follows a page-based routing architecture with responsive dual-layout pattern (mobile/desktop variants), heavy use of tabbed navigation for complex pages, polling-based live data updates, and CRUD-heavy pages with optimistic UI patterns and confirmation dialogs.
|
||||
## tags
|
||||
page, components, workspace, react, features, mobile, hooks, settings
|
||||
page, components, react, workspace, features, mobile, hooks, settings
|
||||
## symbols
|
||||
- WorkspaceDetailPage
|
||||
- WorkspacesPage
|
||||
@@ -41,6 +41,6 @@ page, components, workspace, react, features, mobile, hooks, settings
|
||||
- change pages behavior
|
||||
read: ConfigProfilesPage.tsx, DashboardPage.tsx, GitHistoryPage.tsx
|
||||
- update pages tests
|
||||
read: DashboardPage.test.tsx, ProjectsPage.test.tsx
|
||||
read: DashboardPage.test.tsx, ProjectsPage.test.tsx, WorkspaceDetailPage.test.tsx
|
||||
## dirty
|
||||
-
|
||||
|
||||
@@ -1,89 +0,0 @@
|
||||
import { useState } from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
import { EmptyState, ErrorState, LoadingState } from "../components/data-states";
|
||||
import { useMobileViewport } from "../hooks/use-mobile-viewport";
|
||||
import { useRepoWorkspace } from "../hooks/use-repo-workspace";
|
||||
import { WorkspaceHeader } from "../components/features/workspace/workspace-header";
|
||||
import { WorkspaceLayout } from "../components/features/workspace/WorkspaceLayout";
|
||||
|
||||
type MobileTab = "files" | "editor" | "git" | "terminal";
|
||||
|
||||
export const RepoWorkspace = () => {
|
||||
const {
|
||||
projectId,
|
||||
project,
|
||||
status,
|
||||
repositories,
|
||||
selectedRepoId,
|
||||
selectedRepo,
|
||||
branches,
|
||||
currentBranch,
|
||||
gitStatus,
|
||||
toolTypes,
|
||||
handleRepoChange,
|
||||
handleBranchChange,
|
||||
loadGitStatus,
|
||||
loadBranches,
|
||||
loadRepositories,
|
||||
} = useRepoWorkspace();
|
||||
const isMobile = useMobileViewport();
|
||||
const [mobileTab, setMobileTab] = useState<MobileTab>("files");
|
||||
|
||||
return (
|
||||
<section className="repo-workspace">
|
||||
{project && (
|
||||
<WorkspaceHeader
|
||||
project={project}
|
||||
currentRepo={selectedRepo || null}
|
||||
/>
|
||||
)}
|
||||
|
||||
{status === "loading" && (
|
||||
<LoadingState message="Loading repositories..." />
|
||||
)}
|
||||
|
||||
{status === "error" && (
|
||||
<ErrorState
|
||||
message="Failed to load repositories"
|
||||
onRetry={() => void loadRepositories()}
|
||||
/>
|
||||
)}
|
||||
|
||||
{status === "empty" && (
|
||||
<div className="card stack">
|
||||
<EmptyState message="No repositories in this project yet." />
|
||||
<Link
|
||||
className="primary-button"
|
||||
to={`/projects/${projectId}/settings/repositories`}
|
||||
>
|
||||
Manage Repositories
|
||||
</Link>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{status === "ready" && repositories.length > 0 && (
|
||||
<WorkspaceLayout
|
||||
projectId={projectId!}
|
||||
project={project}
|
||||
isMobile={isMobile}
|
||||
mobileTab={mobileTab}
|
||||
selectedRepoId={selectedRepoId}
|
||||
selectedRepo={selectedRepo}
|
||||
branches={branches}
|
||||
currentBranch={currentBranch}
|
||||
gitStatus={gitStatus}
|
||||
toolTypes={toolTypes}
|
||||
repositories={repositories}
|
||||
onMobileTabChange={setMobileTab}
|
||||
onRepoChange={handleRepoChange}
|
||||
onBranchChange={handleBranchChange}
|
||||
onRefresh={() => {
|
||||
void loadBranches();
|
||||
void loadGitStatus();
|
||||
window.dispatchEvent(new CustomEvent("refresh-file-tree"));
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</section>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,126 @@
|
||||
import "@testing-library/jest-dom/vitest";
|
||||
import { cleanup, fireEvent, render, screen, waitFor } from "@testing-library/react";
|
||||
import { MemoryRouter, Routes, Route } from "react-router-dom";
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
afterEach(() => {
|
||||
cleanup();
|
||||
});
|
||||
|
||||
import { WorkspaceDetailPage } from "./WorkspaceDetailPage";
|
||||
|
||||
const mockWorkspaces = [
|
||||
{
|
||||
id: "ws-1",
|
||||
name: "dev",
|
||||
repo_name: "my-repo",
|
||||
project_name: "My Project",
|
||||
branch: "main",
|
||||
path: "/data/ws/dev",
|
||||
status: "ready",
|
||||
instance_count: 0,
|
||||
},
|
||||
];
|
||||
|
||||
vi.mock("../hooks/use-workspaces", () => ({
|
||||
useWorkspaces: () => ({
|
||||
workspaces: mockWorkspaces,
|
||||
loading: false,
|
||||
error: null,
|
||||
refresh: vi.fn(),
|
||||
}),
|
||||
}));
|
||||
|
||||
vi.mock("../hooks/use-workspace-files", () => ({
|
||||
useWorkspaceFiles: () => ({
|
||||
entries: [],
|
||||
content: null,
|
||||
currentPath: "",
|
||||
loading: false,
|
||||
error: null,
|
||||
loadFile: vi.fn(),
|
||||
saveFile: vi.fn(),
|
||||
navigateTo: vi.fn(),
|
||||
}),
|
||||
}));
|
||||
|
||||
vi.mock("../hooks/use-workspace-git", () => ({
|
||||
useWorkspaceGit: () => ({
|
||||
status: {
|
||||
branch: "main",
|
||||
modified: [],
|
||||
added: [],
|
||||
deleted: [],
|
||||
untracked: [],
|
||||
ahead: 0,
|
||||
behind: 0,
|
||||
},
|
||||
history: [],
|
||||
branches: ["main"],
|
||||
currentBranch: "main",
|
||||
loading: false,
|
||||
error: null,
|
||||
commit: vi.fn(),
|
||||
push: vi.fn(),
|
||||
pull: vi.fn(),
|
||||
fetch: vi.fn(),
|
||||
checkout: vi.fn(),
|
||||
}),
|
||||
}));
|
||||
|
||||
vi.mock("../hooks/use-workspace-instances", () => ({
|
||||
useWorkspaceInstances: () => ({
|
||||
instances: [],
|
||||
loading: false,
|
||||
error: null,
|
||||
refresh: vi.fn(),
|
||||
}),
|
||||
}));
|
||||
|
||||
vi.mock("../hooks/use-mobile-viewport", () => ({
|
||||
useMobileViewport: () => false,
|
||||
}));
|
||||
|
||||
describe("WorkspaceDetailPage", () => {
|
||||
it("renders workspace header and tabs", async () => {
|
||||
render(
|
||||
<MemoryRouter initialEntries={["/workspaces/ws-1"]}>
|
||||
<Routes>
|
||||
<Route
|
||||
path="/workspaces/:workspaceId"
|
||||
element={<WorkspaceDetailPage />}
|
||||
/>
|
||||
</Routes>
|
||||
</MemoryRouter>,
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText("dev")).toBeInTheDocument();
|
||||
});
|
||||
expect(screen.getByText(/my-repo/i)).toBeInTheDocument();
|
||||
expect(screen.getByRole("tab", { name: /files/i })).toBeInTheDocument();
|
||||
expect(screen.getByRole("tab", { name: /git/i })).toBeInTheDocument();
|
||||
expect(screen.getByRole("tab", { name: /tools/i })).toBeInTheDocument();
|
||||
expect(screen.getByRole("tab", { name: /settings/i })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("switches tabs", async () => {
|
||||
render(
|
||||
<MemoryRouter initialEntries={["/workspaces/ws-1"]}>
|
||||
<Routes>
|
||||
<Route
|
||||
path="/workspaces/:workspaceId"
|
||||
element={<WorkspaceDetailPage />}
|
||||
/>
|
||||
</Routes>
|
||||
</MemoryRouter>,
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByRole("tab", { name: /settings/i })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
fireEvent.click(screen.getByRole("tab", { name: /settings/i }));
|
||||
expect(screen.getByText(/workspace info/i)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user