fix(tool): stop defaulting manifest working_dir to /workspace

The /workspace compatibility symlink was removed from the manifest
compiler/entrypoint in 3e59a25. Tool definitions that still set
runtime.working_dir to /workspace therefore start in an empty directory
instead of /home/user/{repo_name}.

- manifest-editor.tsx: default working_dir to empty instead of /workspace;
  update startup-script placeholder to reference /home/alex/.
- Add Alembic migration 2026_06_19_113000 that clears the stale
  runtime.working_dir = /workspace from the built-in pi-agent manifest.
- Add migration import test.

Quality gates: pytest tests/api tests/services/test_terminal_manager_multi.py tests/unit (248 passed), ruff check (clean), npx tsc --noEmit (clean), eslint (clean).
This commit is contained in:
2026-06-19 12:10:42 +02:00
parent 6d7f538a79
commit 10955dfe8e
28 changed files with 235 additions and 123 deletions
+1 -1
View File
@@ -2,7 +2,7 @@
dir: apps/web/src/components
## role
Reusable React UI components providing the application's shared visual elements, layout shell, authentication guards, and notification infrastructure.
Provides reusable, type-safe React UI components for the web application including layout shells, data display states, iconography, code editing, syntax highlighting, authentication guards, and toast notification rules.
## parent
index: apps/web/src/.pi-map.index.md
map: apps/web/src/.pi-map.md
+13 -13
View File
@@ -4,22 +4,22 @@ dir: apps/web/src/components
index: apps/web/src/components/.pi-map.index.md
## role
Reusable React UI components providing the application's shared visual elements, layout shell, authentication guards, and notification infrastructure.
Provides reusable, type-safe React UI components for the web application including layout shells, data display states, iconography, code editing, syntax highlighting, authentication guards, and toast notification rules.
## files
- app-shell.tsx | Provides the main application shell layout with navigation, session management, and responsive mobile/desktop rendering for a React application. | exp: AppShell | dep: react-router-dom, ../api/sessions, ../hooks/use-theme, ../utils/open-session, ../state/auth, ../state/sessions, ../hooks/use-mobile-viewport, ../state/events, ../state/toast, ../state/notifications, ./features/notification/event-toast-bridge, ./features/notification/notification-center, ./icon, ./features/mobile/mobile-nav, ./features/tool/start-tool-fab, ../utils/icons
- code-editor.tsx | A React component that renders a syntax-highlighting code editor with line numbers using react-simple-code-editor. | exp: CodeEditor | dep: react, react-simple-code-editor, ../utils/language
- data-states.tsx | Provides reusable React components for displaying loading, error, and empty data states in a UI. | exp: LoadingState, ErrorState, EmptyState | dep: ./icon, icon
- icon.tsx | Provides a centralized, type-safe React icon component that maps semantic names to Phosphor icons with configurable size, weight, color, and accessibility properties. | exp: IconName, IconProps, Icon | dep: react, @phosphor-icons/react
- loading-overlay.tsx | Renders an accessible loading overlay with optional label when visible prop is true | exp: func:LoadingOverlay({ visible, label }: LoadingOverlayProps) | dep: ./icon, icon
- protected-route.test.tsx | Tests a ProtectedRoute component that guards private content based on authentication state | dep: @testing-library/react, react-router-dom, vitest, ./protected-route, ../state/auth
- protected-route.tsx | A React component that conditionally renders children or redirects to login based on authentication state, preserving the intended destination URL. | exp: ProtectedRoute | dep: react-router-dom, ../state/auth
- syntax-highlighter.tsx | Renders syntax-highlighted code blocks with optional line numbers, language badge, and copy-to-clipboard functionality. | exp: SyntaxHighlighter | dep: react, ./icon, ../utils/language, React, icon, language utilities
- 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 | 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, InstanceEventPayload
- app-shell.tsx | Provides the main application layout shell with navigation, session management, and responsive mobile/desktop views for a React application. | exp: AppShell | dep: react-router-dom, ../api/sessions, ../hooks/use-theme, ../utils/open-session, ../state/auth, ../state/sessions, ../hooks/use-mobile-viewport, ../state/events, ../state/toast, ../state/notifications, ./features/notification/event-toast-bridge, ./features/notification/notification-center, ./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 a lightweight editor library. | exp: CodeEditor | dep: react, react-simple-code-editor, ../utils/language
- data-states.tsx | Defines reusable React components for displaying loading, error, and empty data states in a UI. | exp: LoadingState, ErrorState, EmptyState | dep: ./icon, icon
- icon.tsx | Provides a centralized, type-safe React component that maps semantic icon names to Phosphor icons with configurable size, weight, color, and accessibility properties. | exp: IconName, IconProps, Icon | dep: react, @phosphor-icons/react
- loading-overlay.tsx | Renders an accessible loading spinner overlay with optional text label that conditionally displays based on visibility prop | exp: func:LoadingOverlay({ visible, label }: LoadingOverlayProps) | dep: ./icon, icon
- protected-route.test.tsx | Tests a ProtectedRoute component that guards content based on authentication state | dep: @testing-library/react, react-router-dom, vitest, ./protected-route, ../state/auth
- protected-route.tsx | A React component that conditionally renders children or redirects to login based on authentication state | exp: ProtectedRoute | dep: react-router-dom, ../state/auth
- syntax-highlighter.tsx | A React component that renders syntax-highlighted code blocks with optional line numbers, language badge, and copy-to-clipboard functionality. | exp: SyntaxHighlighter | dep: react, ./icon, ../utils/language
- toast-rules.test.ts | Unit tests for toast notification rules that map instance events to UI categories and severity levels. | 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, InstanceEventPayload
## arch
Component-based architecture with functional React patterns, separation of presentational components (data states, icons, syntax highlighting) from behavioral logic (toast rules, protected routes), and centralized design abstractions (icon mapping, code editor wrapping).
Follows a component-based architecture with presentational/ui components, separation of concerns (display logic vs. business rules in `.ts` files), test co-location, and centralized abstractions for common patterns like icons and data states.
## tags
toast, react, state, icon, code, loading, event, editor
toast, state, react, icon, loading, event, code, editor
## symbols
- LoadingOverlay
- mapEventToCategory
@@ -2,7 +2,7 @@
dir: apps/web/src/components/features
## role
Reusable UI feature components that compose domain-specific functionality for the web application.
Reusable UI components organized by domain-specific feature areas for the web application frontend.
## parent
index: apps/web/src/components/.pi-map.index.md
map: apps/web/src/components/.pi-map.md
+2 -2
View File
@@ -4,10 +4,10 @@ dir: apps/web/src/components/features
index: apps/web/src/components/features/.pi-map.index.md
## role
Reusable UI feature components that compose domain-specific functionality for the web application.
Reusable UI components organized by domain-specific feature areas for the web application frontend.
## files
## arch
Feature-based component organization with domain-driven design patterns, likely combining presentational components with localized state and business logic.
Feature-based component colocation pattern with domain-driven folder structure, separating shared/common components from page-specific feature implementations.
## tags
-
## symbols
@@ -2,7 +2,7 @@
dir: apps/web/src/components/features/tool
## role
Provides UI components for managing containerized development tools, including instance lifecycle management, manifest configuration, and workspace-integrated tool launching.
Provides UI components for managing containerized development tools, including starting, configuring, monitoring, and editing tool instances across workspaces.
## parent
index: apps/web/src/components/features/.pi-map.index.md
map: apps/web/src/components/features/.pi-map.md
@@ -4,18 +4,18 @@ dir: apps/web/src/components/features/tool
index: apps/web/src/components/features/tool/.pi-map.index.md
## role
Provides UI components for managing containerized development tools, including instance lifecycle management, manifest configuration, and workspace-integrated tool launching.
Provides UI components for managing containerized development tools, including starting, configuring, monitoring, and editing tool instances across workspaces.
## files
- instance-list.tsx | React component that 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, ../../loading-overlay, ../../../api/sessions, ../../../api/tool-types, ../session/create-session-form, ../../../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 manifest configurations including base images, packages, environment variables, scripts, mounts, and runtime settings, with live preview generation capability. | exp: ManifestEditor | dep: react, ../../icon, ../../../utils/errors, ../../../api/tool-definitions, icon, errors utils, tool-definitions API
- start-tool-fab.tsx | Renders a floating action button 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, workspace types, workspaces api
- start-tool-modal.tsx | React modal component for selecting a tool type and optional config profile to start a tool 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
- tool-starter.tsx | React component for starting a tool instance within a workspace with configurable tool types, profiles, and SSH keys | exp: ToolStarterProps, func:ToolStarter({ workspace, onStarted, onCancel, }: ToolStarterProps), call:useSessions, 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: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, ../../loading-overlay, ../../../api/tool-types, ../../../api/config-profiles, ../../../api/ssh-keys, ../../../state/sessions, ../../../types/workspace, ../../../api/sessions, icon, loading-overlay, tool-types, config-profiles, ssh-keys, sessions, workspace types, sessions api
- instance-list.tsx | Displays and manages tool instances for a project/repo with start/stop/restart/delete actions, config profile selection, SSH key selection, and real-time status updates. | exp: InstanceList | dep: react, react-router-dom, ../../icon, ../../loading-overlay, ../../../api/sessions, ../../../api/tool-types, ../session/create-session-form, ../../../api/config-profiles, ../../../api/ssh-keys, ../../../state/events, ../../../state/sessions, Icon, LoadingOverlay, sessions API, config-profiles API, ssh-keys API, events state, sessions state
- manifest-editor.tsx | A React component that provides a form-based UI for editing container manifest configurations including base images, packages, environment variables, scripts, mounts, and runtime settings, with live preview compilation. | exp: ManifestEditor | dep: react, ../../icon, ../../../utils/errors, ../../../api/tool-definitions, icon, errors utils, tool-definitions API
- start-tool-fab.tsx | Renders a floating action button that opens a modal for selecting a workspace and starting 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
- start-tool-modal.tsx | A React modal component that allows users to select a tool type and optional configuration profile to start a tool on a specific 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 for starting a tool instance within a workspace context, fetching and configuring tool types, profiles, and SSH keys. | exp: ToolStarterProps, func:ToolStarter({ workspace, onStarted, onCancel, }: ToolStarterProps), call:useSessions, 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: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, ../../loading-overlay, ../../../api/tool-types, ../../../api/config-profiles, ../../../api/ssh-keys, ../../../state/sessions, ../../../types/workspace, ../../../api/sessions, icon, loading-overlay, tool-types, config-profiles, ssh-keys, sessions, 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 React component architecture with modal/bottom-sheet mobile-responsive patterns, form-driven configuration editing with live preview, and workspace-contextual tool orchestration.
React component-based architecture with feature-specific composition, using modal/bottom-sheet patterns for mobile-responsive navigation, form-based configuration editors with live preview, and real-time status polling for instance management.
## tags
call:set, tool, types, start, call:use, ssh, react, loading
call:set, tool, types, start, ssh, call:use, icon, loading
## symbols
- StartToolFAB
- StartToolModal
@@ -49,7 +49,7 @@ export const ManifestEditor = ({
const [startupScripts, setStartupScripts] = useState<string[]>([""]);
const [mounts, setMounts] = useState<MountEntry[]>([]);
const [command, setCommand] = useState<string[]>([""]);
const [workingDir, setWorkingDir] = useState("/workspace");
const [workingDir, setWorkingDir] = useState("");
const [stdinOpen, setStdinOpen] = useState(true);
const [tty, setTty] = useState(true);
@@ -94,7 +94,7 @@ export const ManifestEditor = ({
const runtime = (manifest.runtime as Record<string, unknown>) || {};
setCommand((runtime.command as string[]) || [""]);
setWorkingDir((runtime.working_dir as string) || "/workspace");
setWorkingDir((runtime.working_dir as string) || "");
setStdinOpen((runtime.stdin_open as boolean) ?? true);
setTty((runtime.tty as boolean) ?? true);
}, [manifest]);
@@ -561,7 +561,8 @@ export const ManifestEditor = ({
<textarea
value={script}
onChange={(e) => updateStartupScript(idx, e.target.value)}
placeholder="chown -R user:user /workspace"
placeholder="chown -R user:user $HOME/$WORKSPACE_NAME"
className="form-input font-mono text-sm flex-1"
rows={2}
/>
@@ -723,7 +724,7 @@ export const ManifestEditor = ({
type="text"
value={workingDir}
onChange={(e) => setWorkingDir(e.target.value)}
placeholder="/workspace"
placeholder="e.g., /workspace or ~/code"
className="form-input"
/>
</div>