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:
@@ -2,7 +2,7 @@
|
||||
dir: apps/web/src/components
|
||||
|
||||
## role
|
||||
Provides reusable, foundational React UI components and utilities for the web application, including layout shell, data visualization, navigation guards, and user feedback systems.
|
||||
Provides foundational, reusable UI components and utilities for the web application, including layout shell, data states, icon system, code display, routing guards, and toast notification rules.
|
||||
## parent
|
||||
index: apps/web/src/.pi-map.index.md
|
||||
map: apps/web/src/.pi-map.md
|
||||
|
||||
@@ -4,7 +4,7 @@ dir: apps/web/src/components
|
||||
index: apps/web/src/components/.pi-map.index.md
|
||||
|
||||
## role
|
||||
Provides reusable, foundational React UI components and utilities for the web application, including layout shell, data visualization, navigation guards, and user feedback systems.
|
||||
Provides foundational, reusable UI components and utilities for the web application, including layout shell, data states, icon system, code display, routing guards, and toast notification rules.
|
||||
## files
|
||||
- app-shell.tsx | Renders the main application shell layout with navigation, session management, and responsive mobile/desktop views for a React Router-based app. | exp: AppShell | dep: react-router-dom, ../api/sessions, ../hooks/use-theme, ../state/auth, ../state/sessions, ../hooks/use-mobile-viewport, ../state/events, ../state/toast, ../state/notifications, ../state/session-operations, ./features/notification/event-toast-bridge, ./features/notification/notification-center, ./features/session/session-progress-panel, ./icon, ./features/mobile/mobile-nav, ./features/tool/start-tool-fab, ../utils/icons
|
||||
- code-editor.tsx | A React component that renders a syntax-highlighted code editor with line numbers using react-simple-code-editor. | exp: CodeEditor | dep: react, react-simple-code-editor, ../utils/language
|
||||
@@ -16,7 +16,7 @@ Provides reusable, foundational React UI components and utilities for the web ap
|
||||
- toast-rules.test.ts | Unit tests for mapping instance events to toast notification categories and severities | dep: vitest, ./toast-rules, ../types/events
|
||||
- toast-rules.ts | Maps instance events to toast notifications with deduplication logic to prevent spam | exp: func:mapEventToCategory(event: InstanceEventPayload) → string, call:event.event.startsWith, func:mapEventToSeverity(event: InstanceEventPayload) → "info" | "warning" | "error" | "success", func:handleEventToast(event: InstanceEventPayload) → void, call:shouldShowToast, call:toast.info, call:toast.success, call:toast.warning, call:toast.error, func:clearToastDedup() → void, call:lastToastTime.clear | dep: ../state/toast, ../types/events, toast state module, InstanceEventPayload type
|
||||
## arch
|
||||
Component-based architecture with functional React patterns, composition of specialized sub-components (icon, editor, syntax highlighter), separation of concerns via dedicated state-management components (data states, protected routes), and utility modules with pure logic for cross-cutting concerns (toast rules with deduplication).
|
||||
Follows a component-based React architecture with separation of concerns between presentational components (icon, data-states, code-editor), layout/app-shell orchestration, auth-guarded routing (protected-route), and domain-specific utility modules (toast-rules) with colocated unit tests.
|
||||
## tags
|
||||
toast, state, react, icon, code, event, editor, protected
|
||||
## symbols
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
dir: apps/web/src/components/features
|
||||
|
||||
## role
|
||||
Contains reusable React components that implement specific product features and business logic for the web application.
|
||||
Contains specialized UI components for major feature areas of the web application, organizing components by business domain rather than by atomic design level.
|
||||
## parent
|
||||
index: apps/web/src/components/.pi-map.index.md
|
||||
map: apps/web/src/components/.pi-map.md
|
||||
|
||||
@@ -4,10 +4,10 @@ dir: apps/web/src/components/features
|
||||
index: apps/web/src/components/features/.pi-map.index.md
|
||||
|
||||
## role
|
||||
Contains reusable React components that implement specific product features and business logic for the web application.
|
||||
Contains specialized UI components for major feature areas of the web application, organizing components by business domain rather than by atomic design level.
|
||||
## files
|
||||
## arch
|
||||
Feature-based component organization with domain-specific UI building blocks, likely composed of atomic design elements (from components/ui) and consumed by page-level routes.
|
||||
Feature-based colocation pattern where components are grouped by product functionality (e.g., checkout, dashboard, settings) rather than by component type, typically combining multiple atomic components with domain-specific logic and data fetching.
|
||||
## tags
|
||||
-
|
||||
## symbols
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
dir: apps/web/src/components/features/session
|
||||
|
||||
## role
|
||||
Provides UI components for managing development environment sessions including creation, listing, monitoring progress, and interacting with individual sessions.
|
||||
Provides UI components for managing development sessions including creation, listing, monitoring progress, and interacting with individual sessions.
|
||||
## parent
|
||||
index: apps/web/src/components/features/.pi-map.index.md
|
||||
map: apps/web/src/components/features/.pi-map.md
|
||||
|
||||
@@ -4,16 +4,16 @@ dir: apps/web/src/components/features/session
|
||||
index: apps/web/src/components/features/session/.pi-map.index.md
|
||||
|
||||
## role
|
||||
Provides UI components for managing development environment sessions including creation, listing, monitoring progress, and interacting with individual sessions.
|
||||
Provides UI components for managing development sessions including creation, listing, monitoring progress, and interacting with individual sessions.
|
||||
## files
|
||||
- create-session-form.tsx | React form component for creating development sessions with configurable project, repository, tool type, branch, SSH keys, and config profile options | exp: CreateSessionForm | dep: react, ../../icon, ../../../api/sessions, ../../../types, ../../../api/git-repositories, ../../../api/tool-types, ../../../api/ssh-keys, ../../../api/config-profiles, icon component, sessions API, git-repositories API, ssh-keys API, config-profiles API, tool-types API, types
|
||||
- session-card.tsx | Renders a React card component displaying session information with status badges, inline editing, action buttons, and responsive mobile/desktop layouts. | exp: SessionCardProps, func:SessionCard({ session, onOpen, onStart, onStop, onDelete, onRecreateTunnel, onRename, isBusy = false, tunnelHealth = null, }: SessionCardProps), call:useState, call:useRef, call:useMobileViewport, call:session.tool_type_interfaces?.includes, call:[ "running", "building", "starting", "probing", "pending", "unhealthy", ].includes, call:useEffect, call:optionsRef.current.contains, call:setOptionsOpen, call:document.addEventListener, call:document.removeEventListener, call:window.confirm, call:onDelete, call:setEditName, call:onRename, call:setIsEditingName, call:editName.trim, call:onOpen, call:new Date(session.created_at).toLocaleString, call:setShowActionSheet, call:onStart, call:onStop, call:onRecreateTunnel | dep: react, ../../../api/sessions, ../../icon, ../../../hooks/use-mobile-viewport, ../mobile/mobile-action-sheet, icon, use-mobile-viewport, mobile-action-sheet, sessions api types
|
||||
- create-session-form.tsx | A React form component for creating and starting a new development session with configurable project, repository, workspace, tool type, config profile, and SSH key options. | exp: CreateSessionForm | dep: react, ../../icon, ../../../api/sessions, ../../../types, ../../../api/git-repositories, ../../../api/tool-types, ../../../api/ssh-keys, ../../../api/config-profiles, ../../../api/workspaces, ../../../types/workspace, icon, sessions API, git-repositories API, tool-types API, ssh-keys API, config-profiles API, workspaces API, types
|
||||
- session-card.tsx | Renders a card component displaying session information with status badges, inline rename editing, action buttons, and responsive mobile/desktop layouts including a dropdown options menu and mobile action sheet. | exp: SessionCardProps, func:SessionCard({ session, onOpen, onStart, onStop, onDelete, onRecreateTunnel, onRename, isBusy = false, tunnelHealth = null, }: SessionCardProps), call:useState, call:useRef, call:useMobileViewport, call:session.tool_type_interfaces?.includes, call:[ "running", "building", "starting", "probing", "pending", "unhealthy", ].includes, call:useEffect, call:optionsRef.current.contains, call:setOptionsOpen, call:document.addEventListener, call:document.removeEventListener, call:window.confirm, call:onDelete, call:setEditName, call:onRename, call:setIsEditingName, call:editName.trim, call:onOpen, call:new Date(session.created_at).toLocaleString, call:setShowActionSheet, call:onStart, call:onStop, call:onRecreateTunnel | dep: react, ../../../api/sessions, ../../icon, ../../../hooks/use-mobile-viewport, ../mobile/mobile-action-sheet
|
||||
- session-list.tsx | Renders a list of sessions grouped by active/recent status or as a flat grid, delegating to SessionCard for individual session display. | exp: SessionListProps, func:SessionList({ sessions, onOpen, onStart, onStop, onDelete, onRecreateTunnel, onRename, actionBusyId = null, tunnelHealth = {}, showGrouping = true, activeTitle = "Active Sessions", recentTitle = "Recent Sessions", maxRecent = 5, emptyMessage = "No sessions", }: SessionListProps), call:sessions.filter, call:activeStatuses.includes, call:sessions .filter((s) => recentStatuses.includes(s.status)) .slice, call:recentStatuses.includes, call:sessions.map, call:activeSessions.map, call:recentSessions.map | dep: ../../../api/sessions, ./session-card, Session, SessionCard, InstanceHealth
|
||||
- session-progress-panel.tsx | Renders a panel displaying active and recently completed session operations with step-by-step progress indicators and dismissible notifications. | exp: func:SessionProgressPanel(), call:useSessionOperations, call:useEventContext, call:useEffect, call:updateOperationFromEvent, call:operations.filter, call:Date.now, call:visibleOperations.map, call:dismissOperation | dep: react, ../../../state/session-operations, ../../../state/events, ../../icon, useSessionOperations, useEventContext, Icon
|
||||
## arch
|
||||
Feature-based component composition with presentational components following a container/presenter pattern, using status-driven conditional rendering and responsive layout adaptations.
|
||||
React component composition with feature-specific grouping, responsive design patterns (mobile/desktop layouts), and status-driven conditional rendering with delegated sub-components.
|
||||
## tags
|
||||
session, call:use, call:on, card, mobile, call:set, event, api
|
||||
session, call:use, call:on, api, card, call:set, event, mobile
|
||||
## symbols
|
||||
- SessionCard
|
||||
- SessionList
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -176,14 +176,6 @@ export function SessionCard({
|
||||
)}{" "}
|
||||
· {session.tool_type_name}
|
||||
</p>
|
||||
{session.clone_mode && (
|
||||
<p className="muted session-card-meta">
|
||||
<Icon name="branch" size="sm" />
|
||||
{session.clone_mode === "clone"
|
||||
? `Clone${session.branch ? ` (${session.branch})` : ""}`
|
||||
: "Mount"}
|
||||
</p>
|
||||
)}
|
||||
{session.url && (
|
||||
<p className="session-card-url">
|
||||
<button
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
dir: apps/web/src/components/features/tool
|
||||
|
||||
## role
|
||||
Provides UI components for managing container-based development tools, including instance lifecycle operations, manifest editing, and workspace-integrated tool launching.
|
||||
Provides UI components for managing containerized development tools, including instance lifecycle operations, manifest editing, and workspace-integrated tool launching.
|
||||
## parent
|
||||
index: apps/web/src/components/features/.pi-map.index.md
|
||||
map: apps/web/src/components/features/.pi-map.md
|
||||
|
||||
@@ -4,16 +4,16 @@ dir: apps/web/src/components/features/tool
|
||||
index: apps/web/src/components/features/tool/.pi-map.index.md
|
||||
|
||||
## role
|
||||
Provides UI components for managing container-based development tools, including instance lifecycle operations, manifest editing, and workspace-integrated tool launching.
|
||||
Provides UI components for managing containerized development tools, including instance lifecycle operations, manifest editing, and workspace-integrated tool launching.
|
||||
## files
|
||||
- instance-list.tsx | Displays and manages a list of tool instances with CRUD operations, real-time status updates, and configuration profile selection. | exp: InstanceList | dep: react, react-router-dom, ../../icon, ../../../api/sessions, ../../../api/tool-types, ../session/create-session-form, ../../../api/config-profiles, ../../../api/ssh-keys, ../../../state/events, ../../../state/sessions, icon, api/sessions, api/tool-types, api/config-profiles, api/ssh-keys, state/events, state/sessions
|
||||
- manifest-editor.tsx | A React component that provides a form-based UI for editing container tool definition manifests with fields for base images, packages, user config, environment variables, scripts, mounts, and runtime settings, including compilation preview functionality. | exp: ManifestEditor | dep: react, ../../icon, ../../../utils/errors, ../../../api/tool-definitions, icon, errors, tool-definitions
|
||||
- start-tool-fab.tsx | A floating action button component that opens a modal to select a workspace and start a tool. | exp: func:StartToolFAB(), call:useState, call:setOpen, call:setWorkspacesLoading, call:listAllWorkspaces, call:setWorkspaces, call:setSelectedWorkspace, call:e.stopPropagation, call:workspaces.find, call:workspaces.map | dep: react, ../../icon, ./tool-starter, ../../../types/workspace, ../../../api/workspaces, icon, tool-starter, workspaces api, workspace types
|
||||
- start-tool-modal.tsx | Renders a modal dialog for selecting a tool type and optional config profile to start on a workspace. | exp: StartToolModalProps, func:StartToolModal({ workspace, onClose, onStart, }: StartToolModalProps), call:useState, call:useAsyncData, call:e.preventDefault, call:setError, call:setSubmitting, call:onStart, call:onClose, call:e.stopPropagation, call:setToolTypeId, call:toolTypes?.map, call:setConfigProfileId | dep: react, ../../icon, ../../../api/tool-types, ../../../hooks/use-async-data, ../../../types/workspace, icon, tool-types, use-async-data, workspace
|
||||
- tool-starter.tsx | React component that provides a workspace-first form for starting a new tool instance, fetching tool types, config profiles, and SSH keys dynamically. | exp: ToolStarterProps, func:ToolStarter({ workspace, onStarted, onCancel, }: ToolStarterProps), call:useSessions, call:useSessionOperations, call:useState, call:useEffect, call:listToolTypes, call:setToolTypes, call:setToolTypesError, call:setToolTypesLoading, call:load, call:setProfiles, call:setSelectedProfileId, call:setProfilesLoading, call:listConfigProfiles, call:data.find, call:listSSHKeys, call:setSshKeys, call:setSelectedSshKeyIds, call:console.error, call:setSshKeysLoading, call:sshKeys.find, call:useCallback, call:setError, call:setStarting, call:createInstance, call:displayName.trim, call:startInstance, call:addOrUpdateSession, call:startOperation, call:onStarted, call:setSelectedToolTypeId, call:toolTypes.find, call:setDisplayName, call:toolTypes.map, call:setNameEdited, call:profiles.map, call:sshKeys.map, call:selectedSshKeyIds.includes, call:prev.filter | dep: react, ../../icon, ../../../api/tool-types, ../../../api/config-profiles, ../../../api/ssh-keys, ../../../state/sessions, ../../../state/session-operations, ../../../types/workspace, ../../../api/sessions, icon, tool-types, config-profiles, ssh-keys, sessions, session-operations, workspace
|
||||
- tool-starter.tsx | A React component that provides a workspace-first UI for selecting tool types, config profiles, and SSH keys to create and start a new tool instance/session. | exp: ToolStarterProps, func:ToolStarter({ workspace, onStarted, onCancel, }: ToolStarterProps), call:useSessions, call:useSessionOperations, call:useState, call:useEffect, call:listToolTypes, call:setToolTypes, call:setToolTypesError, call:setToolTypesLoading, call:load, call:setProfiles, call:setSelectedProfileId, call:setProfilesLoading, call:listConfigProfiles, call:data.find, call:listSSHKeys, call:setSshKeys, call:setSelectedSshKeyIds, call:console.error, call:setSshKeysLoading, call:sshKeys.find, call:useCallback, call:setError, call:setStarting, call:createInstance, call:displayName.trim, call:startInstance, call:addOrUpdateSession, call:startOperation, call:onStarted, call:setSelectedToolTypeId, call:toolTypes.find, call:setDisplayName, call:toolTypes.map, call:setNameEdited, call:profiles.map, call:sshKeys.map, call:selectedSshKeyIds.includes, call:prev.filter | dep: react, ../../icon, ../../../api/tool-types, ../../../api/config-profiles, ../../../api/ssh-keys, ../../../state/sessions, ../../../state/session-operations, ../../../types/workspace, ../../../api/sessions, icon, tool-types, config-profiles, ssh-keys, sessions, session-operations, workspace
|
||||
- tools-bottom-sheet.tsx | Renders a mobile bottom sheet navigation menu for tools with active route highlighting | exp: ToolsBottomSheet | dep: react-router-dom, ../../icon, icon
|
||||
## arch
|
||||
Feature-based component architecture with form-driven modals, real-time status integration, and responsive mobile/desktop patterns (bottom sheets vs modals)
|
||||
Feature-based component composition with modal/bottom-sheet navigation patterns, real-time status integration, and form-driven configuration management with preview capabilities.
|
||||
## tags
|
||||
tool, call:set, types, call:use, api, start, ssh, icon
|
||||
## symbols
|
||||
|
||||
@@ -131,12 +131,9 @@ export function ToolStarter({
|
||||
workspace.repo_id,
|
||||
selectedToolTypeId,
|
||||
displayName.trim() || undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
workspace.id,
|
||||
selectedProfileId || undefined,
|
||||
selectedSshKeyIds.length > 0 ? selectedSshKeyIds : undefined,
|
||||
workspace.id,
|
||||
);
|
||||
await startInstance(
|
||||
workspace.project_id,
|
||||
|
||||
Reference in New Issue
Block a user