Compare commits

...

4 Commits

Author SHA1 Message Date
Developer 35f0a3ea2e Merge branch 'fix/mobile-profile-title' into dev 2026-06-13 21:45:33 +00:00
Developer 1ae8d0e45f fix: hide page title on mobile Profile view
The mobile profile form already renders its own header via
ProfileMobileView, so the desktop page title was redundant on small
viewports.

Quality gates: npm run typecheck, npm run lint

Refs: openspec/changes/mobile-tool-profile-ui
2026-06-13 21:45:33 +00:00
Developer 025ccc5817 Merge branch 'feat/mobile-tool-profile-ui' into dev 2026-06-13 21:41:51 +00:00
Developer 350b393457 feat: rework mobile UI for Tool Workshop and Profile pages
- Rework ToolWorkshopMobileView to support full desktop functionality:
  definition type selection (Compose/Dockerfile/Manifest), manifest editor,
  conditional port, startup command, readiness probe, required variables,
  and validation feedback.
- Add ProfileMobileView and wire ProfilePage to render it on mobile.
- Update useToolWorkshop hook to return boolean success from submit.
- Add responsive CSS for mobile forms, edit views, and manifest editor.
- Update project maps.

Quality gates: npm run typecheck, npm run lint, npm test -- --run (87 passed)

Refs: openspec/changes/mobile-tool-profile-ui
2026-06-13 21:41:42 +00:00
28 changed files with 798 additions and 188 deletions
+1 -1
View File
@@ -16,7 +16,7 @@ dir: .
Trust boundary: index routes, map orients, source decides. Trust boundary: index routes, map orients, source decides.
## role ## role
Infrastructure and deployment configuration package for a self-hosted project management platform with OAuth2 authentication, providing containerized orchestration, environment templates, and development tooling. Infrastructure and deployment configuration for a self-hosted project management and git repository platform with OAuth2 authentication.
## parent ## parent
- -
## children ## children
+2 -2
View File
@@ -18,7 +18,7 @@ index: ./.pi-map.index.md
Trust boundary: index routes, map orients, source decides. Trust boundary: index routes, map orients, source decides.
## role ## role
Infrastructure and deployment configuration package for a self-hosted project management platform with OAuth2 authentication, providing containerized orchestration, environment templates, and development tooling. Infrastructure and deployment configuration for a self-hosted project management and git repository platform with OAuth2 authentication.
## files ## files
- .env.example | Provides a template of environment variables for configuring a Headquarter application with PostgreSQL, Redis, Authentik SSO, and Docker/Traefik deployment - .env.example | Provides a template of environment variables for configuring a Headquarter application with PostgreSQL, Redis, Authentik SSO, and Docker/Traefik deployment
- .gitignore | Specifies files and directories for Git to ignore across a multi-language project with Python, Node, and custom tooling | dep: Git - .gitignore | Specifies files and directories for Git to ignore across a multi-language project with Python, Node, and custom tooling | dep: Git
@@ -31,7 +31,7 @@ Infrastructure and deployment configuration package for a self-hosted project ma
- progress.md | Tracks completed and remaining tasks for a backend-frontend code refactoring project organized in 7 phases - progress.md | Tracks completed and remaining tasks for a backend-frontend code refactoring project organized in 7 phases
- swap-pane | Empty file with no functionality - swap-pane | Empty file with no functionality
## arch ## arch
Docker Compose-based microservices architecture with frontend/backend separation, PostgreSQL/Redis data layer, Traefik reverse proxy integration, and environment-driven configuration management following twelve-factor app principles. Containerized microservices architecture using Docker Compose with PostgreSQL/Redis data layer, Traefik reverse proxy for TLS/ingress, and environment-driven configuration across frontend/backend services.
## tags ## tags
docker, redis, git, application, postgresql, compose, traefik, project docker, redis, git, application, postgresql, compose, traefik, project
## symbols ## symbols
+1 -1
View File
@@ -2,7 +2,7 @@
dir: apps dir: apps
## role ## role
Contains the main deployable application modules or entry points for the project. Contains the main deployable application entry points and top-level configurations for the project.
## parent ## parent
index: ./.pi-map.index.md index: ./.pi-map.index.md
map: ./.pi-map.md map: ./.pi-map.md
+2 -2
View File
@@ -4,10 +4,10 @@ dir: apps
index: apps/.pi-map.index.md index: apps/.pi-map.index.md
## role ## role
Contains the main deployable application modules or entry points for the project. Contains the main deployable application entry points and top-level configurations for the project.
## files ## files
## arch ## arch
Modular monolith or microservices architecture with separate application boundaries, each potentially having its own configuration, dependencies, and lifecycle. Monorepo-style app separation with distinct application boundaries, likely sharing common libraries/modules while maintaining independent deployment artifacts.
## tags ## tags
- -
## symbols ## symbols
+1 -1
View File
@@ -2,7 +2,7 @@
dir: apps/web dir: apps/web
## role ## role
Frontend web application providing a React-based UI with code editing, terminal, and routing capabilities for the "headquarter" project. Frontend web application for a React-based developer workspace with code editing, terminal, and routing capabilities.
## parent ## parent
index: apps/.pi-map.index.md index: apps/.pi-map.index.md
map: apps/.pi-map.md map: apps/.pi-map.md
+2 -2
View File
@@ -4,7 +4,7 @@ dir: apps/web
index: apps/web/.pi-map.index.md index: apps/web/.pi-map.index.md
## role ## role
Frontend web application providing a React-based UI with code editing, terminal, and routing capabilities for the "headquarter" project. Frontend web application for a React-based developer workspace with code editing, terminal, and routing capabilities.
## files ## files
- .env.example | Template file defining example environment variables for frontend API and application URL configuration - .env.example | Template file defining example environment variables for frontend API and application URL configuration
- .eslintrc.cjs | Configures ESLint for a TypeScript browser project with modern ECMAScript module support | dep: @typescript-eslint/parser, @typescript-eslint/eslint-plugin, eslint - .eslintrc.cjs | Configures ESLint for a TypeScript browser project with modern ECMAScript module support | dep: @typescript-eslint/parser, @typescript-eslint/eslint-plugin, eslint
@@ -16,7 +16,7 @@ Frontend web application providing a React-based UI with code editing, terminal,
- tsconfig.json | TypeScript configuration file for a React project using Vite with modern ES2020 target and bundler module resolution | dep: typescript, react, vite - tsconfig.json | TypeScript configuration file for a React project using Vite with modern ES2020 target and bundler module resolution | dep: typescript, react, vite
- vite.config.ts | Configures Vite build tool for a React project with custom dev server port and Vitest test settings. | dep: vite, @vitejs/plugin-react - vite.config.ts | Configures Vite build tool for a React project with custom dev server port and Vitest test settings. | dep: vite, @vitejs/plugin-react
## arch ## arch
Modern React SPA built with Vite and TypeScript, using nginx for production serving with client-side routing, multi-stage Docker deployment, and Vitest for testing. Modern Vite-based SPA with TypeScript, nginx reverse proxy serving, multi-stage Docker containerization, and client-side routing.
## tags ## tags
react, eslint, vite, typescript, dom, application, nginx, web react, eslint, vite, typescript, dom, application, nginx, web
## symbols ## symbols
+1 -1
View File
@@ -2,7 +2,7 @@
dir: apps/web/src dir: apps/web/src
## role ## role
Frontend web application entry point and core infrastructure for a React-based collaborative development platform. Entry point and core infrastructure for a React web application handling authentication, routing, and domain type definitions.
## parent ## parent
index: apps/web/.pi-map.index.md index: apps/web/.pi-map.index.md
map: apps/web/.pi-map.md map: apps/web/.pi-map.md
+2 -2
View File
@@ -4,13 +4,13 @@ dir: apps/web/src
index: apps/web/src/.pi-map.index.md index: apps/web/src/.pi-map.index.md
## role ## role
Frontend web application entry point and core infrastructure for a React-based collaborative development platform. Entry point and core infrastructure for a React web application handling authentication, routing, and domain type definitions.
## files ## files
- main.tsx | Bootstraps a React application with routing, authentication, and session management providers. | dep: react, react-dom/client, react-router-dom, ./router, ./state/auth, ./state/sessions, ./styles/tokens.css, ./styles/global.css, ./styles/utilities.css, ./styles/syntax-highlight.css, ./styles/pages/git-history.css, ./styles/pages/projects.css, ./styles/pages/sessions.css, ./styles/pages/ssh-keys.css, ./styles/pages/workspace-detail.css, ./styles/pages/workspaces.css, react-dom - main.tsx | Bootstraps a React application with routing, authentication, and session management providers. | dep: react, react-dom/client, react-router-dom, ./router, ./state/auth, ./state/sessions, ./styles/tokens.css, ./styles/global.css, ./styles/utilities.css, ./styles/syntax-highlight.css, ./styles/pages/git-history.css, ./styles/pages/projects.css, ./styles/pages/sessions.css, ./styles/pages/ssh-keys.css, ./styles/pages/workspace-detail.css, ./styles/pages/workspaces.css, react-dom
- router.tsx | Defines the React Router configuration for a web application with protected routes, nested layouts, and redirects. | exp: AppRouter | dep: react-router-dom, ./components/app-shell, ./components/protected-route, ./pages/DashboardPage, ./pages/PlaceholderPage, ./pages/ProfilePage, ./pages/ProjectsPage, ./pages/GitRepositoriesPage, ./pages/GitHistoryPage, ./pages/ProjectSettingsPage, ./pages/SettingsPage, ./pages/TerminalPage, ./pages/ToolWorkshopPage, ./pages/SshKeysPage, ./pages/ConfigProfilesPage, ./pages/SessionsPage, ./pages/WorkspacesPage, ./pages/WorkspaceDetailPage - router.tsx | Defines the React Router configuration for a web application with protected routes, nested layouts, and redirects. | exp: AppRouter | dep: react-router-dom, ./components/app-shell, ./components/protected-route, ./pages/DashboardPage, ./pages/PlaceholderPage, ./pages/ProfilePage, ./pages/ProjectsPage, ./pages/GitRepositoriesPage, ./pages/GitHistoryPage, ./pages/ProjectSettingsPage, ./pages/SettingsPage, ./pages/TerminalPage, ./pages/ToolWorkshopPage, ./pages/SshKeysPage, ./pages/ConfigProfilesPage, ./pages/SessionsPage, ./pages/WorkspacesPage, ./pages/WorkspaceDetailPage
- types.ts | Defines TypeScript type definitions for user sessions, projects, repositories, and workspaces in an application. | exp: SessionUser, SessionPayload, Project, WorkspaceSummary, RepositorySummary, ProjectWithRepos - types.ts | Defines TypeScript type definitions for user sessions, projects, repositories, and workspaces in an application. | exp: SessionUser, SessionPayload, Project, WorkspaceSummary, RepositorySummary, ProjectWithRepos
## arch ## arch
Modular React SPA using React Router v6 with nested route layouts, protected route guards via authentication context, and centralized TypeScript domain models for session/workspace/project entities. Provider pattern wrapping React Router with nested route layouts, protected route guards, and centralized TypeScript type definitions for session-based auth and workspace/project domain models.
## tags ## tags
pages, styles, css, router, react, session, dom, project pages, styles, css, router, react, session, dom, project
## symbols ## symbols
+1 -1
View File
@@ -2,7 +2,7 @@
dir: apps/web/src/components dir: apps/web/src/components
## role ## role
Provides reusable, accessible UI components and utilities for rendering the application shell, data states, icons, code display, notifications, and route protection in a React web application. Provides reusable, accessible UI primitives and layout components for a React web application, including shell layout, data states, icons, code display, routing guards, and toast notifications.
## parent ## parent
index: apps/web/src/.pi-map.index.md index: apps/web/src/.pi-map.index.md
map: apps/web/src/.pi-map.md map: apps/web/src/.pi-map.md
@@ -2,7 +2,7 @@
dir: apps/web/src/components/features dir: apps/web/src/components/features
## role ## role
Contains reusable React components that implement specific user-facing features and functionality across the web application. Reusable UI feature components that compose domain-specific functionality for the web application
## parent ## parent
index: apps/web/src/components/.pi-map.index.md index: apps/web/src/components/.pi-map.index.md
map: apps/web/src/components/.pi-map.md map: apps/web/src/components/.pi-map.md
@@ -19,6 +19,9 @@ map: apps/web/src/components/.pi-map.md
- apps/web/src/components/features/notification - apps/web/src/components/features/notification
index: apps/web/src/components/features/notification/.pi-map.index.md index: apps/web/src/components/features/notification/.pi-map.index.md
map: apps/web/src/components/features/notification/.pi-map.md map: apps/web/src/components/features/notification/.pi-map.md
- apps/web/src/components/features/profile
index: apps/web/src/components/features/profile/.pi-map.index.md
map: apps/web/src/components/features/profile/.pi-map.md
- apps/web/src/components/features/project - apps/web/src/components/features/project
index: apps/web/src/components/features/project/.pi-map.index.md index: apps/web/src/components/features/project/.pi-map.index.md
map: apps/web/src/components/features/project/.pi-map.md map: apps/web/src/components/features/project/.pi-map.md
@@ -0,0 +1,20 @@
# apps/web/src/components/features/profile (index)
dir: apps/web/src/components/features/profile
## role
Provides a mobile-specific UI for users to view and edit their profile information.
## parent
index: apps/web/src/components/features/.pi-map.index.md
map: apps/web/src/components/features/.pi-map.md
## children
-
## files
- ProfileMobileView.tsx
## links
index: apps/web/src/components/features/profile/.pi-map.index.md
map: apps/web/src/components/features/profile/.pi-map.md
## workflows
- change profile behavior
read: ProfileMobileView.tsx
## dirty
-
@@ -0,0 +1,20 @@
# apps/web/src/components/features/profile
dir: apps/web/src/components/features/profile
index: apps/web/src/components/features/profile/.pi-map.index.md
## role
Provides a mobile-specific UI for users to view and edit their profile information.
## files
- ProfileMobileView.tsx | Renders a mobile-optimized profile editing form with avatar upload, name/email fields, and save/cancel functionality. | exp: ProfileMobileView | dep: react-router-dom, ../mobile/mobile-edit-view, ../../icon, ../../../api/profile
## arch
Single feature-focused component with form handling and file upload, likely using controlled inputs and local state management.
## tags
mobile, profile, view, renders, optimized, editing, form, avatar
## symbols
- ProfileMobileView
## workflows
- change profile behavior
read: ProfileMobileView.tsx
## dirty
-
@@ -0,0 +1,119 @@
import { useNavigate } from "react-router-dom";
import { MobileEditView } from "../mobile/mobile-edit-view";
import { Icon } from "../../icon";
import type { UserProfile } from "../../../api/profile";
interface ProfileMobileViewProps {
profile: UserProfile;
name: string;
email: string;
error: string | null;
isSaving: boolean;
avatarUrl: string | null;
fileInputRef: React.RefObject<HTMLInputElement>;
onNameChange: (value: string) => void;
onEmailChange: (value: string) => void;
onAvatarButtonClick: () => void;
onAvatarChange: (event: React.ChangeEvent<HTMLInputElement>) => void;
onSave: () => void;
}
export const ProfileMobileView = ({
profile,
name,
email,
error,
isSaving,
avatarUrl,
fileInputRef,
onNameChange,
onEmailChange,
onAvatarButtonClick,
onAvatarChange,
onSave,
}: ProfileMobileViewProps) => {
const navigate = useNavigate();
return (
<MobileEditView
title="Profile"
onCancel={() => navigate(-1)}
onSave={onSave}
isSaving={isSaving}
>
<div className="profile-mobile-avatar-section">
<div className="profile-mobile-avatar">
{avatarUrl ? (
<img alt="Avatar" className="profile-mobile-avatar-image" src={avatarUrl} />
) : (
<div className="profile-mobile-avatar-placeholder">
{profile.name.charAt(0).toUpperCase()}
</div>
)}
</div>
<button
className="secondary-button"
disabled={isSaving}
onClick={onAvatarButtonClick}
type="button"
>
{isSaving ? (
<>
<Icon name="loading" size="sm" />
Uploading...
</>
) : (
<>
<Icon name="edit" size="sm" />
Change Avatar
</>
)}
</button>
<input
accept="image/png,image/jpeg"
onChange={onAvatarChange}
ref={fileInputRef}
style={{ display: "none" }}
type="file"
/>
</div>
<div className="mobile-form-group">
<label className="mobile-form-label" htmlFor="profile-mobile-name">
Name *
</label>
<input
className="mobile-form-input"
disabled={isSaving}
id="profile-mobile-name"
onChange={(e) => onNameChange(e.target.value)}
placeholder="Your name"
type="text"
value={name}
/>
</div>
<div className="mobile-form-group">
<label className="mobile-form-label" htmlFor="profile-mobile-email">
Email *
</label>
<input
className="mobile-form-input"
disabled={isSaving}
id="profile-mobile-email"
onChange={(e) => onEmailChange(e.target.value)}
placeholder="your.email@example.com"
type="email"
value={email}
/>
</div>
{error && (
<p className="mobile-form-error">
<Icon name="warning" size="sm" />
{error}
</p>
)}
</MobileEditView>
);
};
@@ -2,7 +2,7 @@
dir: apps/web/src/components/features/tool-workshop dir: apps/web/src/components/features/tool-workshop
## role ## role
Provides a complete UI for managing custom tool types in a "Tool Workshop" interface with list, edit, and mobile-responsive views. Provides a complete CRUD interface for managing reusable tool type definitions (Docker Compose, Dockerfile, Manifest) in a workshop-style admin panel with responsive desktop and mobile layouts.
## parent ## parent
index: apps/web/src/components/features/.pi-map.index.md index: apps/web/src/components/features/.pi-map.index.md
map: apps/web/src/components/features/.pi-map.md map: apps/web/src/components/features/.pi-map.md
@@ -4,15 +4,15 @@ dir: apps/web/src/components/features/tool-workshop
index: apps/web/src/components/features/tool-workshop/.pi-map.index.md index: apps/web/src/components/features/tool-workshop/.pi-map.index.md
## role ## role
Provides a complete UI for managing custom tool types in a "Tool Workshop" interface with list, edit, and mobile-responsive views. Provides a complete CRUD interface for managing reusable tool type definitions (Docker Compose, Dockerfile, Manifest) in a workshop-style admin panel with responsive desktop and mobile layouts.
## files ## files
- ToolTypeEditorPanel.tsx | Renders a form panel for creating or editing tool types with support for compose, dockerfile, and manifest definition types | exp: ToolTypeFormState, ToolTypeEditorPanel | dep: ../../icon, ../tool/manifest-editor, ../../../api/tool-types, ../../../api/tool-definitions, React, Icon, ManifestEditor - ToolTypeEditorPanel.tsx | Renders a form panel for creating or editing tool types with support for compose, dockerfile, and manifest definition types | exp: ToolTypeFormState, ToolTypeEditorPanel | dep: ../../icon, ../tool/manifest-editor, ../../../api/tool-types, ../../../api/tool-definitions, React, Icon, ManifestEditor
- ToolTypeListSidebar.tsx | Renders a sidebar component for listing, selecting, creating, and deleting tool types in a "Tool Workshop" interface. | exp: ToolTypeListSidebar | dep: ../../icon, ../../../api/tool-types, React, Icon component, ToolType type - ToolTypeListSidebar.tsx | Renders a sidebar component for listing, selecting, creating, and deleting tool types in a "Tool Workshop" interface. | exp: ToolTypeListSidebar | dep: ../../icon, ../../../api/tool-types, React, Icon component, ToolType type
- ToolWorkshopMobileView.tsx | Renders a mobile-responsive view for managing tool types with list, detail, and edit modes | exp: MobileView, ToolWorkshopMobileView | dep: ../mobile/mobile-list-view, ../mobile/mobile-detail-view, ../mobile/mobile-edit-view, ../mobile/mobile-fab, ../../../api/tool-types, ./ToolTypeEditorPanel, MobileListView, MobileDetailView, MobileEditView, MobileFAB, ToolType, ToolTypeFormState - ToolWorkshopMobileView.tsx | Renders a mobile-responsive three-view (list/detail/edit) interface for managing tool types in a tool workshop, with form handling for creating and editing tool configurations including Docker Compose, Dockerfile, or Manifest definitions. | exp: MobileView, ToolWorkshopMobileView | dep: react, ../mobile/mobile-list-view, ../mobile/mobile-detail-view, ../mobile/mobile-edit-view, ../mobile/mobile-fab, ../tool/manifest-editor, ../../icon, ../../../api/tool-types, ./ToolTypeEditorPanel, ../../../api/tool-definitions, mobile-list-view, mobile-detail-view, mobile-edit-view, mobile-fab, manifest-editor, icon, tool-types, ToolTypeEditorPanel, tool-definitions
## arch ## arch
Uses a split-pane sidebar/detail panel pattern with dedicated mobile breakpoint handling, separating list navigation from form editing concerns across three specialized view components. Compound component pattern with split-pane desktop layout (sidebar list + editor panel) and state-driven mobile view with three-view routing (list/detail/edit), using React state for form management and optimistic UI updates.
## tags ## tags
tool, mobile, type, view, types, list, panel, editor tool, mobile, view, type, types, editor, list, workshop
## symbols ## symbols
- ToolTypeFormState - ToolTypeFormState
- ToolTypeEditorPanel - ToolTypeEditorPanel
@@ -1,9 +1,14 @@
import { useState } from "react";
import { MobileListView } from "../mobile/mobile-list-view"; import { MobileListView } from "../mobile/mobile-list-view";
import { MobileDetailView } from "../mobile/mobile-detail-view"; import { MobileDetailView } from "../mobile/mobile-detail-view";
import { MobileEditView } from "../mobile/mobile-edit-view"; import { MobileEditView } from "../mobile/mobile-edit-view";
import { MobileFAB } from "../mobile/mobile-fab"; import { MobileFAB } from "../mobile/mobile-fab";
import { ManifestEditor } from "../tool/manifest-editor";
import { Icon } from "../../icon";
import type { ToolType } from "../../../api/tool-types"; import type { ToolType } from "../../../api/tool-types";
import type { ToolTypeFormState } from "./ToolTypeEditorPanel"; import type { ToolTypeFormState } from "./ToolTypeEditorPanel";
import type { ToolDefinitionManifest } from "../../../api/tool-definitions";
export type MobileView = "list" | "detail" | "edit"; export type MobileView = "list" | "detail" | "edit";
@@ -13,13 +18,18 @@ interface ToolWorkshopMobileViewProps {
mobileView: MobileView; mobileView: MobileView;
isCreating: boolean; isCreating: boolean;
toolTypeForm: ToolTypeFormState; toolTypeForm: ToolTypeFormState;
manifestData: Record<string, unknown> | null;
manifestDefinitionId: string | null;
baseDefinitions: ToolDefinitionManifest[];
toolTypeError: string | null; toolTypeError: string | null;
toolTypeDirty: boolean;
onViewChange: (view: MobileView) => void; onViewChange: (view: MobileView) => void;
onSelect: (toolType: ToolType) => void; onSelect: (toolType: ToolType) => void;
onCreate: () => void; onCreate: () => void;
onDelete: (id: string) => void; onDelete: (id: string) => void;
onFormChange: (changes: Partial<ToolTypeFormState>) => void; onFormChange: (changes: Partial<ToolTypeFormState>) => void;
onSubmit: () => void; onManifestChange: (manifest: Record<string, unknown> | null) => void;
onSubmit: (e?: React.FormEvent) => Promise<boolean>;
onCancel: () => void; onCancel: () => void;
} }
@@ -29,15 +39,41 @@ export const ToolWorkshopMobileView = ({
mobileView, mobileView,
isCreating, isCreating,
toolTypeForm, toolTypeForm,
manifestData,
manifestDefinitionId,
baseDefinitions,
toolTypeError, toolTypeError,
toolTypeDirty,
onViewChange, onViewChange,
onSelect, onSelect,
onCreate, onCreate,
onDelete, onDelete,
onFormChange, onFormChange,
onManifestChange,
onSubmit, onSubmit,
onCancel, onCancel,
}: ToolWorkshopMobileViewProps) => { }: ToolWorkshopMobileViewProps) => {
const [isSaving, setIsSaving] = useState(false);
const handleSave = async () => {
setIsSaving(true);
try {
const ok = await onSubmit();
if (ok) {
onViewChange("list");
}
} finally {
setIsSaving(false);
}
};
const handleDelete = () => {
if (selectedToolType) {
void onDelete(selectedToolType.id);
onViewChange("list");
}
};
if (mobileView === "list") { if (mobileView === "list") {
return ( return (
<div className="mobile-page"> <div className="mobile-page">
@@ -49,7 +85,7 @@ export const ToolWorkshopMobileView = ({
items={toolTypes.map((t) => ({ items={toolTypes.map((t) => ({
id: t.id, id: t.id,
title: t.display_name, title: t.display_name,
subtitle: `${t.category || "Uncategorized"} · ${t.interface_type === "web" ? `Port ${t.default_port}` : "Terminal"}`, subtitle: `${t.category || "Uncategorized"} · ${t.definition_type} · ${t.interface_type === "web" ? `Port ${t.default_port}` : "Terminal"}`,
}))} }))}
onItemClick={(id) => { onItemClick={(id) => {
const toolType = toolTypes.find((t) => t.id === id); const toolType = toolTypes.find((t) => t.id === id);
@@ -71,11 +107,7 @@ export const ToolWorkshopMobileView = ({
} }
if (mobileView === "detail" && selectedToolType) { if (mobileView === "detail" && selectedToolType) {
return ( const fields = [
<MobileDetailView
title={selectedToolType.display_name}
subtitle={`${selectedToolType.name} · ${selectedToolType.definition_type} · ${selectedToolType.interface_type === "web" ? `Port ${selectedToolType.default_port}` : "Terminal"}`}
fields={[
{ label: "Name", value: selectedToolType.name }, { label: "Name", value: selectedToolType.name },
{ label: "Display Name", value: selectedToolType.display_name }, { label: "Display Name", value: selectedToolType.display_name },
{ label: "Description", value: selectedToolType.description }, { label: "Description", value: selectedToolType.description },
@@ -84,9 +116,11 @@ export const ToolWorkshopMobileView = ({
{ {
label: "Requires Port", label: "Requires Port",
value: selectedToolType.requires_port, value: selectedToolType.requires_port,
type: "boolean", type: "boolean" as const,
}, },
{ label: "Default Port", value: selectedToolType.default_port }, ...(selectedToolType.requires_port
? [{ label: "Default Port", value: selectedToolType.default_port }]
: []),
{ {
label: "Definition Type", label: "Definition Type",
value: selectedToolType.definition_type, value: selectedToolType.definition_type,
@@ -111,24 +145,32 @@ export const ToolWorkshopMobileView = ({
label: "Required Variables", label: "Required Variables",
value: selectedToolType.required_variables?.join(", ") ?? null, value: selectedToolType.required_variables?.join(", ") ?? null,
}, },
...(selectedToolType.definition_type !== "manifest"
? [
{ {
label: "Compose Template", label:
value: selectedToolType.compose_template, selectedToolType.definition_type === "compose"
type: "code", ? "Compose Template"
: "Dockerfile Template",
value:
selectedToolType.definition_type === "compose"
? selectedToolType.compose_template
: selectedToolType.dockerfile_template,
type: "code" as const,
}, },
{ ]
label: "Dockerfile Template", : []),
value: selectedToolType.dockerfile_template, ];
type: "code",
}, return (
]} <MobileDetailView
title={selectedToolType.display_name}
subtitle={`${selectedToolType.name} · ${selectedToolType.definition_type}`}
fields={fields}
onEdit={() => { onEdit={() => {
onViewChange("edit"); onViewChange("edit");
}} }}
onDelete={() => { onDelete={handleDelete}
void onDelete(selectedToolType.id);
onViewChange("list");
}}
onBack={() => { onBack={() => {
onViewChange("list"); onViewChange("list");
}} }}
@@ -137,18 +179,39 @@ export const ToolWorkshopMobileView = ({
} }
if (mobileView === "edit") { if (mobileView === "edit") {
const templateValue =
toolTypeForm.definition_type === "compose"
? toolTypeForm.compose_template
: toolTypeForm.dockerfile_template;
return ( return (
<MobileEditView <MobileEditView
title={isCreating ? "Create Tool Type" : "Edit Tool Type"} title={isCreating ? "Create Tool Type" : "Edit Tool Type"}
onCancel={onCancel} onCancel={onCancel}
onSave={() => { onSave={handleSave}
onSubmit(); isSaving={isSaving}
if (!toolTypeError) {
onViewChange("list");
}
}}
isSaving={false}
> >
<div className="mobile-form-group">
<label className="mobile-form-label">Definition Type</label>
<select
value={toolTypeForm.definition_type}
onChange={(e) =>
onFormChange({
definition_type: e.target.value as
| "compose"
| "dockerfile"
| "manifest",
})
}
className="mobile-form-select"
disabled={!isCreating}
>
<option value="compose">Docker Compose</option>
<option value="dockerfile">Dockerfile</option>
<option value="manifest">Manifest (Declarative)</option>
</select>
</div>
<div className="mobile-form-group"> <div className="mobile-form-group">
<label className="mobile-form-label">Name *</label> <label className="mobile-form-label">Name *</label>
<input <input
@@ -157,8 +220,11 @@ export const ToolWorkshopMobileView = ({
onChange={(e) => onFormChange({ name: e.target.value })} onChange={(e) => onFormChange({ name: e.target.value })}
className="mobile-form-input" className="mobile-form-input"
placeholder="e.g., my-tool" placeholder="e.g., my-tool"
disabled={!isCreating}
required
/> />
</div> </div>
<div className="mobile-form-group"> <div className="mobile-form-group">
<label className="mobile-form-label">Display Name *</label> <label className="mobile-form-label">Display Name *</label>
<input <input
@@ -167,8 +233,10 @@ export const ToolWorkshopMobileView = ({
onChange={(e) => onFormChange({ display_name: e.target.value })} onChange={(e) => onFormChange({ display_name: e.target.value })}
className="mobile-form-input" className="mobile-form-input"
placeholder="e.g., My Tool" placeholder="e.g., My Tool"
required
/> />
</div> </div>
<div className="mobile-form-group"> <div className="mobile-form-group">
<label className="mobile-form-label">Description</label> <label className="mobile-form-label">Description</label>
<textarea <textarea
@@ -179,6 +247,7 @@ export const ToolWorkshopMobileView = ({
rows={3} rows={3}
/> />
</div> </div>
<div className="mobile-form-group"> <div className="mobile-form-group">
<label className="mobile-form-label">Category</label> <label className="mobile-form-label">Category</label>
<input <input
@@ -189,130 +258,179 @@ export const ToolWorkshopMobileView = ({
placeholder="e.g., development" placeholder="e.g., development"
/> />
</div> </div>
<div className="mobile-form-group"> <div className="mobile-form-group">
<label className="mobile-form-label">Interface Type</label> <label className="mobile-form-label">Interface Type</label>
<select <select
value={toolTypeForm.interface_type} value={toolTypeForm.interface_type}
onChange={(e) => onChange={(e) => {
const value = e.target.value as "web" | "terminal";
onFormChange({ onFormChange({
interface_type: e.target.value as "web" | "terminal", interface_type: value,
}) requires_port: value === "web",
} default_port: value === "web" ? toolTypeForm.default_port : "",
});
}}
className="mobile-form-select" className="mobile-form-select"
> >
<option value="web">Web</option> <option value="web">Web</option>
<option value="terminal">Terminal</option> <option value="terminal">Terminal</option>
</select> </select>
</div> </div>
<div className="mobile-form-group">
<label className="mobile-form-label">Requires Port</label> <div className="mobile-form-group mobile-form-row">
<label className="mobile-form-label mobile-form-checkbox-label">
<input <input
type="checkbox" type="checkbox"
checked={toolTypeForm.requires_port} checked={toolTypeForm.requires_port}
onChange={(e) => onFormChange({ requires_port: e.target.checked })} onChange={(e) =>
onFormChange({ requires_port: e.target.checked })
}
className="mobile-form-checkbox" className="mobile-form-checkbox"
/> />
Requires Port
</label>
</div> </div>
{toolTypeForm.requires_port && (
<div className="mobile-form-group"> <div className="mobile-form-group">
<label className="mobile-form-label">Default Port</label> <label className="mobile-form-label">Default Port *</label>
<input <input
type="text" type="number"
value={toolTypeForm.default_port} value={toolTypeForm.default_port}
onChange={(e) => onFormChange({ default_port: e.target.value })} onChange={(e) =>
onFormChange({ default_port: e.target.value })
}
className="mobile-form-input" className="mobile-form-input"
placeholder="e.g., 8080" placeholder="e.g., 8080"
required
/> />
</div> </div>
<div className="mobile-form-group"> )}
<label className="mobile-form-label">Definition Type</label>
<select {toolTypeForm.interface_type === "terminal" && (
value={toolTypeForm.definition_type}
onChange={(e) =>
onFormChange({
definition_type: e.target.value as "compose" | "dockerfile",
})
}
className="mobile-form-select"
>
<option value="compose">Compose</option>
<option value="dockerfile">Dockerfile</option>
</select>
</div>
<div className="mobile-form-group"> <div className="mobile-form-group">
<label className="mobile-form-label">Startup Command</label> <label className="mobile-form-label">Startup Command</label>
<input <input
type="text" type="text"
value={toolTypeForm.startup_command} value={toolTypeForm.startup_command}
onChange={(e) => onFormChange({ startup_command: e.target.value })} onChange={(e) =>
onFormChange({ startup_command: e.target.value })
}
className="mobile-form-input" className="mobile-form-input"
placeholder="Command to run on startup" placeholder="Command to run on startup"
/> />
<small className="mobile-form-help">
Command to run before the interactive shell for each new
terminal session.
</small>
</div> </div>
)}
{toolTypeForm.definition_type === "manifest" ? (
<div className="mobile-manifest-editor-wrapper">
<ManifestEditor
manifest={manifestData}
baseDefinitions={baseDefinitions}
onChange={(m) => onManifestChange(m)}
definitionId={manifestDefinitionId}
/>
</div>
) : (
<div className="mobile-form-group">
<label className="mobile-form-label">
{toolTypeForm.definition_type === "compose"
? "Compose Template *"
: "Dockerfile Template *"}
</label>
<textarea
value={templateValue}
onChange={(e) => {
if (toolTypeForm.definition_type === "compose") {
onFormChange({ compose_template: e.target.value });
} else {
onFormChange({ dockerfile_template: e.target.value });
}
}}
className="mobile-form-textarea mobile-form-code"
placeholder={
toolTypeForm.definition_type === "compose"
? "version: '3'"
: "FROM ubuntu:22.04"
}
rows={10}
required
/>
</div>
)}
<div className="mobile-form-group"> <div className="mobile-form-group">
<label className="mobile-form-label">Readiness Command</label> <label className="mobile-form-label">Readiness Command</label>
<input <input
type="text" type="text"
value={toolTypeForm.readiness_command} value={toolTypeForm.readiness_command}
onChange={(e) => onFormChange({ readiness_command: e.target.value })} onChange={(e) =>
onFormChange({ readiness_command: e.target.value })
}
className="mobile-form-input" className="mobile-form-input"
placeholder="e.g., curl -f http://localhost:8080/health" placeholder="e.g., curl -f http://localhost:8080/health"
/> />
</div> </div>
<div className="mobile-form-group"> <div className="mobile-form-group">
<label className="mobile-form-label">Readiness Timeout</label> <label className="mobile-form-label">Readiness Timeout (seconds)</label>
<input <input
type="text" type="number"
value={toolTypeForm.readiness_timeout} value={toolTypeForm.readiness_timeout}
onChange={(e) => onFormChange({ readiness_timeout: e.target.value })} onChange={(e) =>
onFormChange({ readiness_timeout: e.target.value })
}
className="mobile-form-input" className="mobile-form-input"
placeholder="30" placeholder="30"
/> />
</div> </div>
<div className="mobile-form-group"> <div className="mobile-form-group">
<label className="mobile-form-label">Readiness Interval</label> <label className="mobile-form-label">Readiness Interval (seconds)</label>
<input <input
type="text" type="number"
value={toolTypeForm.readiness_interval} value={toolTypeForm.readiness_interval}
onChange={(e) => onFormChange({ readiness_interval: e.target.value })} onChange={(e) =>
onFormChange({ readiness_interval: e.target.value })
}
className="mobile-form-input" className="mobile-form-input"
placeholder="2" placeholder="2"
/> />
</div> </div>
<div className="mobile-form-group"> <div className="mobile-form-group">
<label className="mobile-form-label">Required Variables</label> <label className="mobile-form-label">Required Variables</label>
<input <input
type="text" type="text"
value={toolTypeForm.required_variables} value={toolTypeForm.required_variables}
onChange={(e) => onFormChange({ required_variables: e.target.value })} onChange={(e) =>
onFormChange({ required_variables: e.target.value })
}
className="mobile-form-input" className="mobile-form-input"
placeholder="VAR1, VAR2, VAR3" placeholder="VAR1, VAR2, VAR3"
/> />
</div> </div>
{toolTypeForm.definition_type === "compose" && (
<div className="mobile-form-group"> {toolTypeError && (
<label className="mobile-form-label">Compose Template</label> <p className="mobile-form-error">
<textarea <Icon name="warning" size="sm" /> {toolTypeError}
value={toolTypeForm.compose_template} </p>
onChange={(e) => onFormChange({ compose_template: e.target.value })}
className="mobile-form-textarea mobile-form-code"
placeholder="version: '3'"
rows={10}
/>
</div>
)} )}
{toolTypeForm.definition_type === "dockerfile" && (
<div className="mobile-form-group"> {toolTypeDirty && (
<label className="mobile-form-label">Dockerfile Template</label> <button
<textarea type="button"
value={toolTypeForm.dockerfile_template} className="secondary-button mobile-discard-button"
onChange={(e) => onClick={onCancel}
onFormChange({ dockerfile_template: e.target.value }) disabled={isSaving}
} >
className="mobile-form-textarea mobile-form-code" Discard Changes
placeholder="FROM ubuntu:22.04" </button>
rows={10}
/>
</div>
)} )}
</MobileEditView> </MobileEditView>
); );
@@ -328,7 +446,7 @@ export const ToolWorkshopMobileView = ({
items={toolTypes.map((t) => ({ items={toolTypes.map((t) => ({
id: t.id, id: t.id,
title: t.display_name, title: t.display_name,
subtitle: `${t.category || "Uncategorized"} · ${t.interface_type === "web" ? `Port ${t.default_port}` : "Terminal"}`, subtitle: `${t.category || "Uncategorized"} · ${t.definition_type} · ${t.interface_type === "web" ? `Port ${t.default_port}` : "Terminal"}`,
}))} }))}
onItemClick={(id) => { onItemClick={(id) => {
const toolType = toolTypes.find((t) => t.id === id); const toolType = toolTypes.find((t) => t.id === id);
+1 -1
View File
@@ -2,7 +2,7 @@
dir: apps/web/src/hooks dir: apps/web/src/hooks
## role ## role
A collection of custom React hooks providing reusable state management, API integration, and UI behavior logic for the web application. Provides a collection of reusable React custom hooks that encapsulate domain-specific business logic, API interactions, and UI state management for the web application.
## parent ## parent
index: apps/web/src/.pi-map.index.md index: apps/web/src/.pi-map.index.md
map: apps/web/src/.pi-map.md map: apps/web/src/.pi-map.md
+6 -6
View File
@@ -4,15 +4,15 @@ dir: apps/web/src/hooks
index: apps/web/src/hooks/.pi-map.index.md index: apps/web/src/hooks/.pi-map.index.md
## role ## role
A collection of custom React hooks providing reusable state management, API integration, and UI behavior logic for the web application. Provides a collection of reusable React custom hooks that encapsulate domain-specific business logic, API interactions, and UI state management for the web application.
## files ## files
- use-async-data.ts | A custom React hook that manages asynchronous data fetching with loading, error, and ready states, plus a manual reload capability. | exp: func:useAsyncData(fetcher: () => Promise<T>, deps: React.DependencyList) → UseAsyncDataResult<T>, call:useState, call:useCallback, call:setStatus, call:setError, call:fetcher, call:setData, call:load, call:useEffect | dep: react - use-async-data.ts | A custom React hook that manages asynchronous data fetching with loading, error, and ready states, plus a manual reload capability. | exp: func:useAsyncData(fetcher: () => Promise<T>, deps: React.DependencyList) → UseAsyncDataResult<T>, call:useState, call:useCallback, call:setStatus, call:setError, call:fetcher, call:setData, call:load, call:useEffect | dep: react
- use-auto-hide.ts | A React custom hook that automatically hides an element after a specified timeout and provides manual controls for showing, hiding, and toggling visibility. | exp: func:useAutoHide(options: AutoHideOptions), call:useState, call:useRef, call:Date.now, call:useCallback, call:setIsVisible, call:clearTimeout, call:setTimeout, call:hide, call:show, call:useEffect | dep: react - use-auto-hide.ts | A React custom hook that automatically hides an element after a specified timeout and provides manual controls for showing, hiding, and toggling visibility. | exp: func:useAutoHide(options: AutoHideOptions), call:useState, call:useRef, call:Date.now, call:useCallback, call:setIsVisible, call:clearTimeout, call:setTimeout, call:hide, call:show, call:useEffect | dep: react
- use-config-profiles.ts | A React custom hook that manages config profile CRUD operations, form state, profile inclusion with cycle detection, and drag-and-drop reordering. | exp: useConfigProfiles | dep: react, ../utils/errors, ../api/config-profiles, ../api/projects, ../api/tool-types, ../types - use-config-profiles.ts | A React custom hook that manages config profile CRUD operations, form state, profile inclusion with cycle detection, and drag-and-drop reordering. | exp: useConfigProfiles | dep: react, ../utils/errors, ../api/config-profiles, ../api/projects, ../api/tool-types, ../types
- use-events.test.ts | Tests a React custom hook that manages Server-Sent Events connections with automatic reconnection, backoff strategies, and error handling. | dep: vitest, @testing-library/react, ./use-events, ../api/events, use-events hook, ../api/events module - use-events.test.ts | Tests a React hook that manages Server-Sent Events (SSE) connections with automatic reconnection, backoff, and error handling | dep: vitest, @testing-library/react, ./use-events, ../api/events, use-events hook
- use-events.ts | React hook that manages a Server-Sent Events connection with exponential backoff reconnection, event buffering, and auth/rate-limit handling. | exp: UseEventsReturn, func:useEvents() → UseEventsReturn, call:useState, call:useRef, call:useCallback, call:clearTimeout, call:createEventSource, call:setConnected, call:setError, call:setReconnectCount, call:JSON.parse, call:setEvents, call:es.close, call:Math.min, call:Math.pow, call:Math.random, call:setTimeout, call:probeEventStreamStatus, call:window.location.assign, call:connect, call:useEffect, call:esRef.current.close | dep: react, ../api/events, ../types/events - use-events.ts | React hook that manages a Server-Sent Events connection with automatic exponential backoff reconnection, lifecycle event handling, and authentication redirect on 401 errors. | exp: UseEventsReturn, func:useEvents() → UseEventsReturn, call:useState, call:useRef, call:useCallback, call:clearTimeout, call:createEventSource, call:setConnected, call:setError, call:setReconnectCount, call:JSON.parse, call:setEvents, call:es.addEventListener, call:es.close, call:Math.min, call:Math.pow, call:Math.random, call:setTimeout, call:probeEventStreamStatus, call:window.location.assign, call:connect, call:useEffect, call:esRef.current.close | dep: react, ../api/events, ../types/events
- use-git-repo.ts | A custom React hook that centralizes all git repository operations (branch management, status tracking, commit history, and git actions) into a reusable interface for components. | exp: GitStatus, UseGitRepoResult, func:useGitRepo(projectId: string | undefined, repoId: string | undefined) → UseGitRepoResult, call:useState, call:useCallback, call:setLoading, call:setError, call:fn, call:extractError, call:withLoading, call:listRepositoryBranches, call:setBranches, call:data.branches.map, call:setDefaultBranch, call:getRepositoryStatus, call:setStatus, call:getRepositoryHistory, call:setHistory, call:getCommitDetail, call:setCommitDetail, call:commitChanges, call:refreshStatus, call:pushRepository, call:pullRepository, call:fetchRepository, call:checkoutBranch, call:refreshBranches, call:createBranch, call:deleteBranch, call:mergeBranches, call:refreshHistory, call:useEffect, raise:err | dep: react, ../api/git-repositories - use-git-repo.ts | A custom React hook that centralizes all git repository operations (branch management, status tracking, commit history, and git actions) into a reusable interface for components. | exp: GitStatus, UseGitRepoResult, func:useGitRepo(projectId: string | undefined, repoId: string | undefined) → UseGitRepoResult, call:useState, call:useCallback, call:setLoading, call:setError, call:fn, call:extractError, call:withLoading, call:listRepositoryBranches, call:setBranches, call:data.branches.map, call:setDefaultBranch, call:getRepositoryStatus, call:setStatus, call:getRepositoryHistory, call:setHistory, call:getCommitDetail, call:setCommitDetail, call:commitChanges, call:refreshStatus, call:pushRepository, call:pullRepository, call:fetchRepository, call:checkoutBranch, call:refreshBranches, call:createBranch, call:deleteBranch, call:mergeBranches, call:refreshHistory, call:useEffect, raise:err | dep: react, ../api/git-repositories
- use-instance-actions.ts | A custom React hook that provides a centralized interface for managing instance/session actions including opening, starting, stopping, deleting, force-deleting, recreating tunnels, and renaming, with loading states and dirty delete handling for conflict resolution. | exp: func:useInstanceActions(options: UseInstanceActionsOptions) → UseInstanceActionsReturn, call:useSessions, call:useSessionOperations, call:useState, call:useRef, call:useCallback, call:tabRefs.current.get, call:existing.focus, call:session.tool_type_interfaces?.includes, call:window.open, call:tabRefs.current.set, call:setLoadingSessionId, call:startOperation, call:startInstance, call:onRefresh, call:completeOperation, call:stopInstance, call:deleteInstance, call:setDirtyDeleteSession, call:setDirtyDeleteFiles, call:removeSession, call:recreateInstanceTunnel, call:alert, call:newName.trim, call:renameInstance | dep: react, ../api/sessions, ../state/sessions, ../state/session-operations - use-instance-actions.ts | A React custom hook that manages instance/session actions including opening, starting, stopping, deleting, force-deleting, recreating tunnels, and renaming with loading states and dirty delete handling. | exp: func:useInstanceActions(options: UseInstanceActionsOptions) → UseInstanceActionsReturn, call:useSessions, call:useState, call:useRef, call:useCallback, call:tabRefs.current.get, call:existing.focus, call:session.tool_type_interfaces?.includes, call:window.open, call:tabRefs.current.set, call:setLoadingSessionId, call:startInstance, call:onRefresh, call:stopInstance, call:deleteInstance, call:setDirtyDeleteSession, call:setDirtyDeleteFiles, call:removeSession, call:recreateInstanceTunnel, call:alert, call:newName.trim, call:renameInstance | dep: react, ../api/sessions, ../state/sessions
- use-mobile-viewport.ts | A React hook that tracks whether the viewport width is below a mobile breakpoint (768px) | exp: func:useMobileViewport(), call:useState, call:useEffect, call:setIsMobile, call:window.addEventListener, call:window.removeEventListener | dep: react - use-mobile-viewport.ts | A React hook that tracks whether the viewport width is below a mobile breakpoint (768px) | exp: func:useMobileViewport(), call:useState, call:useEffect, call:setIsMobile, call:window.addEventListener, call:window.removeEventListener | dep: react
- use-notifications.test.tsx | Tests the useNotifications custom React hook with mocked API calls, covering optimistic updates, polling behavior, and error handling. | dep: vitest, @testing-library/react, ./use-notifications, ../state/notifications, ../api/notifications, use-notifications - use-notifications.test.tsx | Tests the useNotifications custom React hook with mocked API calls, covering optimistic updates, polling behavior, and error handling. | dep: vitest, @testing-library/react, ./use-notifications, ../state/notifications, ../api/notifications, use-notifications
- use-notifications.ts | Custom React hook that provides access to notification state and ensures it's used within a NotificationProvider | exp: func:useNotifications(), call:useContext, raise:Error | dep: react, ../state/notifications - use-notifications.ts | Custom React hook that provides access to notification state and ensures it's used within a NotificationProvider | exp: func:useNotifications(), call:useContext, raise:Error | dep: react, ../state/notifications
@@ -23,7 +23,7 @@ A collection of custom React hooks providing reusable state management, API inte
- use-terminal-page.ts | Manages terminal page state including sessions, keyboard shortcuts, fullscreen mode, mobile viewport handling, and terminal lifecycle operations. | exp: useTerminalPage | dep: react, react-router-dom, ../components/features/terminal/terminal, ../components/features/terminal/terminal-session-tabs, ./use-mobile-viewport, ./use-auto-hide, ./use-virtual-keyboard, ./use-terminal-sessions, ../api/terminal, ./use-special-keys, use-mobile-viewport, use-auto-hide, use-virtual-keyboard, use-terminal-sessions, terminal, terminal-session-tabs, api/terminal, api/sessions, use-special-keys - use-terminal-page.ts | Manages terminal page state including sessions, keyboard shortcuts, fullscreen mode, mobile viewport handling, and terminal lifecycle operations. | exp: useTerminalPage | dep: react, react-router-dom, ../components/features/terminal/terminal, ../components/features/terminal/terminal-session-tabs, ./use-mobile-viewport, ./use-auto-hide, ./use-virtual-keyboard, ./use-terminal-sessions, ../api/terminal, ./use-special-keys, use-mobile-viewport, use-auto-hide, use-virtual-keyboard, use-terminal-sessions, terminal, terminal-session-tabs, api/terminal, api/sessions, use-special-keys
- use-terminal-sessions.ts | React custom hook that manages terminal session state (CRUD operations, active session tracking) for a given instance | exp: UseTerminalSessionsResult, func:useTerminalSessions(instanceId: string) → UseTerminalSessionsResult, call:useState, call:useCallback, call:setLoading, call:setError, call:listTerminalSessions, call:setSessions, call:setActiveSessionId, call:createTerminalSession, call:closeTerminalSession, call:prev.filter, call:renameTerminalSession, call:prev.map, call:resetTerminalSession, call:loadSessions, call:useEffect | dep: react, ../api/terminal - use-terminal-sessions.ts | React custom hook that manages terminal session state (CRUD operations, active session tracking) for a given instance | exp: UseTerminalSessionsResult, func:useTerminalSessions(instanceId: string) → UseTerminalSessionsResult, call:useState, call:useCallback, call:setLoading, call:setError, call:listTerminalSessions, call:setSessions, call:setActiveSessionId, call:createTerminalSession, call:closeTerminalSession, call:prev.filter, call:renameTerminalSession, call:prev.map, call:resetTerminalSession, call:loadSessions, call:useEffect | dep: react, ../api/terminal
- use-theme.ts | React hook that fetches user theme preference on mount and applies it to the document root element via data-theme attribute | exp: func:useTheme(), call:useEffect, call:getUserConfig, call:document.documentElement.removeAttribute, call:document.documentElement.setAttribute | dep: react, ../api/settings - use-theme.ts | React hook that fetches user theme preference on mount and applies it to the document root element via data-theme attribute | exp: func:useTheme(), call:useEffect, call:getUserConfig, call:document.documentElement.removeAttribute, call:document.documentElement.setAttribute | dep: react, ../api/settings
- use-tool-workshop.ts | React custom hook that manages state and operations for a tool workshop UI, including CRUD operations for tool types and tool definitions with form handling and validation. | exp: useToolWorkshop | dep: react, ../utils/errors, ../api/tool-types, ../api/tool-definitions, ../components/features/tool-workshop/ToolTypeEditorPanel - use-tool-workshop.ts | Custom React hook that manages state and operations for a tool workshop UI, including CRUD operations for tool types and tool definitions with form handling and validation. | exp: useToolWorkshop | dep: react, ../utils/errors, ../api/tool-types, ../api/tool-definitions, ../components/features/tool-workshop/ToolTypeEditorPanel
- use-virtual-keyboard.ts | React hook that detects virtual keyboard open/close state and measures its height on mobile devices | exp: func:useVirtualKeyboard(), call:useState, call:useCallback, call:setState, call:useEffect, call:visualViewport.addEventListener, call:window.addEventListener, call:updateKeyboardState, call:visualViewport.removeEventListener, call:window.removeEventListener | dep: react - use-virtual-keyboard.ts | React hook that detects virtual keyboard open/close state and measures its height on mobile devices | exp: func:useVirtualKeyboard(), call:useState, call:useCallback, call:setState, call:useEffect, call:visualViewport.addEventListener, call:window.addEventListener, call:updateKeyboardState, call:visualViewport.removeEventListener, call:window.removeEventListener | dep: react
- use-workspace-actions.ts | Provides a React hook that encapsulates workspace CRUD operations with loading state management and user confirmation dialogs for destructive actions. | exp: UseWorkspaceActionsResult, func:useWorkspaceActions() → UseWorkspaceActionsResult, call:useState, call:useCallback, call:createWorkspace, call:setLoadingId, call:deleteWorkspace, call:onRefresh, call:window.confirm, call:instances.map((i) => `- ${i.name}`).join, call:syncWorkspace, call:updateWorkspace, raise:err | dep: react, ../api/workspaces, ../types/workspace - use-workspace-actions.ts | Provides a React hook that encapsulates workspace CRUD operations with loading state management and user confirmation dialogs for destructive actions. | exp: UseWorkspaceActionsResult, func:useWorkspaceActions() → UseWorkspaceActionsResult, call:useState, call:useCallback, call:createWorkspace, call:setLoadingId, call:deleteWorkspace, call:onRefresh, call:window.confirm, call:instances.map((i) => `- ${i.name}`).join, call:syncWorkspace, call:updateWorkspace, raise:err | dep: react, ../api/workspaces, ../types/workspace
- use-workspace-files.ts | Custom React hook for managing workspace file operations including listing, loading, saving, and navigating files. | exp: UseWorkspaceFilesResult, func:useWorkspaceFiles(workspaceId: string) → UseWorkspaceFilesResult, call:useState, call:useCallback, call:setLoading, call:setError, call:listWorkspaceFiles, call:setEntries, call:setCurrentPath, call:setContent, call:getWorkspaceFileContent, call:saveWorkspaceFile, call:refresh, call:useEffect | dep: react, ../api/workspace-files - use-workspace-files.ts | Custom React hook for managing workspace file operations including listing, loading, saving, and navigating files. | exp: UseWorkspaceFilesResult, func:useWorkspaceFiles(workspaceId: string) → UseWorkspaceFilesResult, call:useState, call:useCallback, call:setLoading, call:setError, call:listWorkspaceFiles, call:setEntries, call:setCurrentPath, call:setContent, call:getWorkspaceFileContent, call:saveWorkspaceFile, call:refresh, call:useEffect | dep: react, ../api/workspace-files
@@ -31,7 +31,7 @@ A collection of custom React hooks providing reusable state management, API inte
- use-workspace-instances.ts | Custom React hook for managing workspace instances with CRUD operations, loading states, and error handling. | exp: UseWorkspaceInstancesResult, func:useWorkspaceInstances(workspaceId: string) → UseWorkspaceInstancesResult, call:useState, call:useCallback, call:setLoading, call:setError, call:listWorkspaceInstances, call:setInstances, call:createWorkspaceInstance, call:refresh, call:useEffect | dep: react, ../api/workspace-instances, ../api/sessions - use-workspace-instances.ts | Custom React hook for managing workspace instances with CRUD operations, loading states, and error handling. | exp: UseWorkspaceInstancesResult, func:useWorkspaceInstances(workspaceId: string) → UseWorkspaceInstancesResult, call:useState, call:useCallback, call:setLoading, call:setError, call:listWorkspaceInstances, call:setInstances, call:createWorkspaceInstance, call:refresh, call:useEffect | dep: react, ../api/workspace-instances, ../api/sessions
- use-workspaces.ts | Custom React hook that fetches and manages workspace data with loading and error states. | exp: UseWorkspacesResult, func:useWorkspaces(projectId: string, repoId: string) → UseWorkspacesResult, call:useState, call:useCallback, call:setLoading, call:setError, call:listWorkspaces, call:listAllWorkspaces, call:setWorkspaces, call:useEffect, call:refresh | dep: react, ../api/workspaces, ../types/workspace - use-workspaces.ts | Custom React hook that fetches and manages workspace data with loading and error states. | exp: UseWorkspacesResult, func:useWorkspaces(projectId: string, repoId: string) → UseWorkspacesResult, call:useState, call:useCallback, call:setLoading, call:setError, call:listWorkspaces, call:listAllWorkspaces, call:setWorkspaces, call:useEffect, call:refresh | dep: react, ../api/workspaces, ../types/workspace
## arch ## arch
Follows a feature-based composition pattern where each hook encapsulates a specific domain concern (data fetching, CRUD operations, terminal/session management, UI interactions), often combining React state with API calls, side effects, and provider context integration. Follows a feature-based composition pattern where each hook is a self-contained unit managing specific concerns (data fetching, CRUD operations, lifecycle management, UI behaviors) using React primitives (useState, useEffect, useCallback) with consistent patterns for loading/error states, optimistic updates, and cleanup; hooks are granular and single-responsibility, often wrapping TanStack Query or direct API calls, with some hooks providing imperative controls and others integrating with browser APIs (SSE, viewport, keyboard, theme).
## tags ## tags
call:set, call:use, workspace, react, state, terminal, api, callback call:set, call:use, workspace, react, state, terminal, api, callback
## symbols ## symbols
+7 -5
View File
@@ -151,13 +151,13 @@ export const useToolWorkshop = () => {
resetToolTypeForm(); resetToolTypeForm();
}; };
const handleToolTypeSubmit = async (e?: React.FormEvent) => { const handleToolTypeSubmit = async (e?: React.FormEvent): Promise<boolean> => {
e?.preventDefault(); e?.preventDefault();
setToolTypeError(null); setToolTypeError(null);
if (!toolTypeForm.name.trim() || !toolTypeForm.display_name.trim()) { if (!toolTypeForm.name.trim() || !toolTypeForm.display_name.trim()) {
setToolTypeError("Name and display name are required"); setToolTypeError("Name and display name are required");
return; return false;
} }
if ( if (
@@ -166,7 +166,7 @@ export const useToolWorkshop = () => {
isNaN(Number(toolTypeForm.default_port))) isNaN(Number(toolTypeForm.default_port)))
) { ) {
setToolTypeError("Default port is required and must be a number"); setToolTypeError("Default port is required and must be a number");
return; return false;
} }
if (toolTypeForm.definition_type !== "manifest") { if (toolTypeForm.definition_type !== "manifest") {
@@ -179,13 +179,13 @@ export const useToolWorkshop = () => {
setToolTypeError( setToolTypeError(
`${toolTypeForm.definition_type === "compose" ? "Compose" : "Dockerfile"} template is required`, `${toolTypeForm.definition_type === "compose" ? "Compose" : "Dockerfile"} template is required`,
); );
return; return false;
} }
} else if (!manifestData) { } else if (!manifestData) {
setToolTypeError( setToolTypeError(
"Manifest data is required for manifest definition type", "Manifest data is required for manifest definition type",
); );
return; return false;
} }
const variables = toolTypeForm.required_variables const variables = toolTypeForm.required_variables
@@ -307,8 +307,10 @@ export const useToolWorkshop = () => {
setToolTypeDirty(false); setToolTypeDirty(false);
} }
await loadData(); await loadData();
return true;
} catch (err) { } catch (err) {
setToolTypeError(extractErrorMessage(err)); setToolTypeError(extractErrorMessage(err));
return false;
} }
}; };
+1 -1
View File
@@ -2,7 +2,7 @@
dir: apps/web/src/pages dir: apps/web/src/pages
## role ## role
Contains top-level page components that serve as route endpoints for the web application's primary UI surfaces, each handling a specific domain area (dashboard, projects, workspaces, git, settings, etc.) with responsive layouts and CRUD operations. Contains top-level page components that serve as route endpoints for the web application, each managing a distinct functional domain of the developer workspace platform.
## parent ## parent
index: apps/web/src/.pi-map.index.md index: apps/web/src/.pi-map.index.md
map: apps/web/src/.pi-map.md map: apps/web/src/.pi-map.md
+7 -7
View File
@@ -4,7 +4,7 @@ dir: apps/web/src/pages
index: apps/web/src/pages/.pi-map.index.md index: apps/web/src/pages/.pi-map.index.md
## role ## role
Contains top-level page components that serve as route endpoints for the web application's primary UI surfaces, each handling a specific domain area (dashboard, projects, workspaces, git, settings, etc.) with responsive layouts and CRUD operations. Contains top-level page components that serve as route endpoints for the web application, each managing a distinct functional domain of the developer workspace platform.
## files ## 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 - 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 | Unit tests for the DashboardPage component verifying overview loading and error retry behavior | dep: @testing-library/jest-dom/vitest, @testing-library/react, react-router-dom, vitest, ./DashboardPage, ../state/sessions, DashboardPage, SessionsProvider - DashboardPage.test.tsx | Unit tests for the DashboardPage component verifying overview loading and error retry behavior | dep: @testing-library/jest-dom/vitest, @testing-library/react, react-router-dom, vitest, ./DashboardPage, ../state/sessions, DashboardPage, SessionsProvider
@@ -12,22 +12,22 @@ Contains top-level page components that serve as route endpoints for the web app
- 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 - 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 - 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
- PlaceholderPage.tsx | Exports three simple React page components (PlaceholderPage, NotFoundPage, LoginRedirectPage) for a frontend scaffold. | exp: PlaceholderPage, NotFoundPage, LoginRedirectPage | dep: ../components/icon, React - PlaceholderPage.tsx | Exports three simple React page components (PlaceholderPage, NotFoundPage, LoginRedirectPage) for a frontend scaffold. | exp: PlaceholderPage, NotFoundPage, LoginRedirectPage | dep: ../components/icon, React
- ProfilePage.tsx | A React component that displays and allows editing of a user profile, including name, email, and avatar upload with validation. | exp: ProfilePage | dep: react, ../api/profile, ../components/data-states, ../components/icon, ../state/auth, ../hooks/use-async-data - ProfilePage.tsx | A React component that renders a responsive profile page with form editing, avatar upload, and save functionality for both mobile and desktop viewports. | exp: ProfilePage | dep: react, ../api/profile, ../components/data-states, ../components/icon, ../components/features/profile/ProfileMobileView, ../state/auth, ../hooks/use-async-data, ../hooks/use-mobile-viewport
- 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 - 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.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 management, and workspace actions. | 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 - ProjectsPage.tsx | Renders a responsive projects management page with separate mobile and desktop layouts, supporting project CRUD operations, repository management, and workspace actions. | 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, use-mobile-viewport, use-projects, data-states, icon, ProjectCard, ProjectDialog, RepositoryCreateDialog, MobileListView, MobileFAB
- 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 - 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 - 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 - 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 - 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 - ToolWorkshopPage.tsx | Renders a responsive tool workshop page with sidebar navigation and editor panel for managing tool types, with mobile-specific view switching | 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
- WorkspaceDetailPage.test.tsx | Tests the WorkspaceDetailPage component rendering 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.test.tsx | Tests the WorkspaceDetailPage component rendering 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 tab-based navigation for files, git, tools, and settings panels, with mobile-responsive layout. | exp: func:WorkspaceDetailPage(), call:useParams, call:useState, call:useMobileViewport, call:useWorkspaces, call:workspaces.find | dep: react, react-router-dom, ../hooks/use-workspaces, ../hooks/use-mobile-viewport, ../components/features/workspace/workspace-detail-header, ../components/features/workspace/workspace-tab-bar, ../components/features/workspace/workspace-file-panel, ../components/features/workspace/workspace-git-panel, ../components/features/workspace/workspace-tools-panel, ../components/features/workspace/workspace-settings-panel, use-workspaces, use-mobile-viewport, workspace-detail-header, workspace-tab-bar, workspace-file-panel, workspace-git-panel, workspace-tools-panel, workspace-settings-panel - WorkspaceDetailPage.tsx | Renders a workspace detail page with tab-based navigation for files, git, tools, and settings panels, with mobile-responsive layout. | exp: func:WorkspaceDetailPage(), call:useParams, call:useState, call:useMobileViewport, call:useWorkspaces, call:workspaces.find | dep: react, react-router-dom, ../hooks/use-workspaces, ../hooks/use-mobile-viewport, ../components/features/workspace/workspace-detail-header, ../components/features/workspace/workspace-tab-bar, ../components/features/workspace/workspace-file-panel, ../components/features/workspace/workspace-git-panel, ../components/features/workspace/workspace-tools-panel, ../components/features/workspace/workspace-settings-panel, use-workspaces, use-mobile-viewport, workspace-detail-header, workspace-tab-bar, workspace-file-panel, workspace-git-panel, workspace-tools-panel, workspace-settings-panel
- WorkspacesPage.tsx | Renders a responsive workspaces management page with separate mobile and desktop layouts, supporting workspace listing, creation, deletion, sync, 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: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-detail-view, ../components/features/mobile/mobile-fab, ../components/features/tool/tool-starter, ../types/workspace - WorkspacesPage.tsx | Renders a responsive workspaces management page with mobile and desktop views supporting listing, creating, viewing details, syncing, deleting, and starting tools for workspaces. | 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: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-detail-view, ../components/features/mobile/mobile-fab, ../components/features/tool/tool-starter, ../types/workspace
## arch ## arch
Follows a page-based routing architecture where each file maps to a URL route, using responsive design patterns with explicit mobile/desktop view branching, compound component layouts (sidebar/editor, tabbed panels), polling for real-time data, and direct API integration within page components rather than abstracted service layers. Follows a page-based routing architecture with responsive mobile/desktop split layouts, heavy use of tabbed navigation and sidebar+editor panel patterns, polling-based real-time data updates, and CRUD-heavy container components with embedded API logic.
## tags ## tags
page, components, workspace, features, react, mobile, settings, hooks page, components, workspace, features, react, mobile, hooks, settings
## symbols ## symbols
- WorkspaceDetailPage - WorkspaceDetailPage
- WorkspacesPage - WorkspacesPage
+21 -2
View File
@@ -3,13 +3,16 @@ import { useCallback, useEffect, useRef, useState } from "react";
import { getProfile, updateProfile, uploadAvatar } from "../api/profile"; import { getProfile, updateProfile, uploadAvatar } from "../api/profile";
import { ErrorState, LoadingState } from "../components/data-states"; import { ErrorState, LoadingState } from "../components/data-states";
import { Icon } from "../components/icon"; import { Icon } from "../components/icon";
import { ProfileMobileView } from "../components/features/profile/ProfileMobileView";
import { useAuth } from "../state/auth"; import { useAuth } from "../state/auth";
import { useAsyncData } from "../hooks/use-async-data"; import { useAsyncData } from "../hooks/use-async-data";
import { useMobileViewport } from "../hooks/use-mobile-viewport";
import type { UserProfile } from "../api/profile"; import type { UserProfile } from "../api/profile";
type ProfileStatus = "loading" | "ready" | "error" | "saving"; type ProfileStatus = "loading" | "ready" | "error" | "saving";
export const ProfilePage = () => { export const ProfilePage = () => {
const isMobile = useMobileViewport();
const { refreshSession } = useAuth(); const { refreshSession } = useAuth();
const { data: profile, status: loadStatus, reload } = useAsyncData<UserProfile>(getProfile, []); const { data: profile, status: loadStatus, reload } = useAsyncData<UserProfile>(getProfile, []);
const [displayStatus, setDisplayStatus] = useState<ProfileStatus>("loading"); const [displayStatus, setDisplayStatus] = useState<ProfileStatus>("loading");
@@ -90,13 +93,29 @@ export const ProfilePage = () => {
return ( return (
<section className="stack"> <section className="stack">
<h1>Profile</h1> {!isMobile && <h1>Profile</h1>}
{displayStatus === "loading" && <LoadingState message="Loading profile..." />} {displayStatus === "loading" && <LoadingState message="Loading profile..." />}
{displayStatus === "error" && <ErrorState message="Failed to load profile" onRetry={reload} />} {displayStatus === "error" && <ErrorState message="Failed to load profile" onRetry={reload} />}
{(displayStatus === "ready" || displayStatus === "saving") && profile && ( {(displayStatus === "ready" || displayStatus === "saving") && profile && (
isMobile ? (
<ProfileMobileView
profile={profile}
name={name}
email={email}
error={error}
isSaving={displayStatus === "saving"}
avatarUrl={avatarUrl}
fileInputRef={fileInputRef}
onNameChange={setName}
onEmailChange={setEmail}
onAvatarButtonClick={() => fileInputRef.current?.click()}
onAvatarChange={handleAvatarChange}
onSave={() => void handleSave()}
/>
) : (
<div className="card stack"> <div className="card stack">
<div className="profile-avatar-section"> <div className="profile-avatar-section">
<div className="avatar-preview"> <div className="avatar-preview">
@@ -178,7 +197,7 @@ export const ProfilePage = () => {
</button> </button>
</div> </div>
</div> </div>
)} ))}
</section> </section>
); );
}; };
+8
View File
@@ -57,12 +57,20 @@ export const ToolWorkshopPage = () => {
mobileView={mobileView} mobileView={mobileView}
isCreating={isCreating} isCreating={isCreating}
toolTypeForm={toolTypeForm} toolTypeForm={toolTypeForm}
manifestData={manifestData}
manifestDefinitionId={manifestDefinitionId}
baseDefinitions={baseDefinitions}
toolTypeError={toolTypeError} toolTypeError={toolTypeError}
toolTypeDirty={toolTypeDirty}
onViewChange={setMobileView} onViewChange={setMobileView}
onSelect={handleSelectToolType} onSelect={handleSelectToolType}
onCreate={handleCreateNew} onCreate={handleCreateNew}
onDelete={handleDeleteToolType} onDelete={handleDeleteToolType}
onFormChange={handleFormChange} onFormChange={handleFormChange}
onManifestChange={(m) => {
setManifestData(m);
setToolTypeDirty(true);
}}
onSubmit={handleToolTypeSubmit} onSubmit={handleToolTypeSubmit}
onCancel={() => { onCancel={() => {
if (toolTypeDirty) { if (toolTypeDirty) {
+1 -1
View File
@@ -2,7 +2,7 @@
dir: apps/web/src/styles dir: apps/web/src/styles
## role ## role
Provides the complete visual design system and styling foundation for the web application, encompassing global styles, design tokens, utility classes, and component-specific styles. Provides the complete visual design system and styling foundation for the web application, encompassing global styles, theme tokens, syntax highlighting, and utility classes.
## parent ## parent
index: apps/web/src/.pi-map.index.md index: apps/web/src/.pi-map.index.md
map: apps/web/src/.pi-map.md map: apps/web/src/.pi-map.md
+3 -3
View File
@@ -4,14 +4,14 @@ dir: apps/web/src/styles
index: apps/web/src/styles/.pi-map.index.md index: apps/web/src/styles/.pi-map.index.md
## role ## role
Provides the complete visual design system and styling foundation for the web application, encompassing global styles, design tokens, utility classes, and component-specific styles. Provides the complete visual design system and styling foundation for the web application, encompassing global styles, theme tokens, syntax highlighting, and utility classes.
## files ## files
- global.css | Defines global CSS styles for a web application shell layout, navigation, cards, forms, dialogs, settings pages, and responsive design patterns. | dep: CSS custom properties (CSS variables: --border, --panel, --brand, --muted, --ink, --bg, --danger, --success, --warning, --font-size-xs, --font-size-sm, --space-2, --space-3, --space-4, --space-5) - global.css | Defines global CSS styles for a web application shell layout, navigation, cards, forms, dialogs, settings pages, and responsive design patterns. | dep: CSS custom properties (CSS variables: --border, --panel, --brand, --muted, --ink, --bg, --danger, --success, --warning, --font-size-xs, --font-size-sm, --space-2, --space-3, --space-4, --space-5)
- syntax-highlight.css | Stylesheet for a syntax highlighting component with toolbar, line numbers, code display, and Prism.js theme integration | dep: Prism.js - syntax-highlight.css | Stylesheet for a syntax highlighting component with toolbar, line numbers, code display, and Prism.js theme integration | dep: Prism.js
- tokens.css | Defines a comprehensive CSS design token system with light/dark themes, spacing scales, breakpoints, and fluid typography for a web application. - tokens.css | Defines a comprehensive CSS design token system with light/dark themes, spacing scales, breakpoints, and fluid typography for a web application.
- utilities.css | Provides responsive CSS utility classes and component-specific styles for a web application featuring terminals, dialogs, tables, forms, and navigation with mobile-first breakpoints | dep: CSS custom properties (variables like --space-*, --border, --bg, --brand, --muted, --success, --danger, --text-xs, --text-sm), xterm.js (terminal library) - utilities.css | Provides responsive CSS utility classes and component-specific styles for a web application featuring terminals, dialogs, tables, forms, and navigation. | dep: CSS custom properties (variables), xterm.js (terminal integration)
## arch ## arch
CSS custom properties (variables) based theming system with light/dark mode support, mobile-first responsive breakpoints, fluid typography scales, utility-first class patterns, and modular separation of concerns across tokens, utilities, global layouts, and component-specific styles. CSS custom properties-based design token architecture with theme-aware variables, modular separation of concerns across global/base/syntax/utility layers, and responsive breakpoint system with fluid typography scaling.
## tags ## tags
space, global, css, web, application, syntax, defines, styles space, global, css, web, application, syntax, defines, styles
## symbols ## symbols
+267 -1
View File
@@ -2109,6 +2109,166 @@ a.nav-item,
background: transparent; background: transparent;
} }
/* Mobile Page */
.mobile-page {
padding: var(--space-2);
padding-bottom: calc(var(--space-4) + 64px);
min-height: 100%;
}
/* Mobile Edit View */
.mobile-edit-view {
display: flex;
flex-direction: column;
min-height: 100%;
background: var(--bg);
}
.mobile-edit-header {
position: sticky;
top: 0;
z-index: 10;
display: flex;
align-items: center;
justify-content: space-between;
gap: var(--space-2);
padding: var(--space-2) var(--space-3);
background: var(--panel);
border-bottom: 1px solid var(--border);
}
.mobile-edit-cancel,
.mobile-edit-save {
min-height: 44px;
padding: var(--space-2) var(--space-3);
border-radius: 10px;
font: inherit;
font-size: 0.9375rem;
cursor: pointer;
}
.mobile-edit-cancel {
background: transparent;
border: 1px solid var(--border);
color: var(--text);
}
.mobile-edit-save {
background: var(--brand);
border: 1px solid var(--brand);
color: white;
}
.mobile-edit-save:disabled,
.mobile-edit-cancel:disabled {
opacity: 0.6;
cursor: not-allowed;
}
.mobile-edit-title {
font-size: 1rem;
font-weight: 600;
margin: 0;
flex: 1;
text-align: center;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.mobile-edit-form {
flex: 1;
overflow-y: auto;
padding: var(--space-3);
padding-bottom: calc(var(--space-4) + 64px);
}
.mobile-edit-field {
margin-bottom: var(--space-3);
}
.mobile-edit-field-label {
display: block;
font-size: 0.875rem;
font-weight: 500;
margin-bottom: var(--space-2);
}
.mobile-edit-input,
.mobile-edit-textarea,
.mobile-edit-select {
width: 100%;
padding: var(--space-3);
border: 1px solid var(--border);
border-radius: 10px;
background: var(--panel);
color: var(--text);
font: inherit;
min-height: 44px;
}
.mobile-edit-textarea {
resize: vertical;
min-height: 96px;
}
.mobile-edit-code {
font-family: "IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, Monaco,
Consolas, monospace;
font-size: 0.8125rem;
}
.mobile-edit-checkbox {
display: flex;
align-items: center;
gap: var(--space-2);
cursor: pointer;
}
.mobile-edit-checkbox input {
width: 22px;
height: 22px;
accent-color: var(--brand);
}
/* Mobile Profile */
.profile-mobile-avatar-section {
display: flex;
flex-direction: column;
align-items: center;
gap: var(--space-3);
padding: var(--space-6) var(--space-3);
margin-bottom: var(--space-2);
}
.profile-mobile-avatar {
width: 96px;
height: 96px;
border-radius: 50%;
overflow: hidden;
background: var(--bg);
border: 2px solid var(--border);
flex-shrink: 0;
}
.profile-mobile-avatar-image {
width: 100%;
height: 100%;
object-fit: cover;
}
.profile-mobile-avatar-placeholder {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
font-size: 2.5rem;
font-weight: 600;
color: var(--muted);
background: var(--bg);
}
/* Mobile Page Header */ /* Mobile Page Header */
.mobile-page-header { .mobile-page-header {
display: flex; display: flex;
@@ -2179,13 +2339,119 @@ a.nav-item,
/* Mobile form group (inline fields) */ /* Mobile form group (inline fields) */
.mobile-form-group { .mobile-form-group {
padding: var(--space-2); padding: var(--space-3);
background: var(--panel); background: var(--panel);
border-radius: 8px; border-radius: 8px;
border: 1px solid var(--border); border: 1px solid var(--border);
margin-bottom: var(--space-2); margin-bottom: var(--space-2);
} }
.mobile-form-label {
display: block;
font-size: 0.875rem;
font-weight: 500;
margin-bottom: var(--space-2);
color: var(--text);
}
.mobile-form-input,
.mobile-form-select,
.mobile-form-textarea {
width: 100%;
padding: var(--space-3);
border: 1px solid var(--border);
border-radius: 10px;
background: var(--bg);
color: var(--text);
font: inherit;
min-height: 44px;
}
.mobile-form-textarea {
resize: vertical;
min-height: 96px;
}
.mobile-form-code {
font-family: "IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, Monaco,
Consolas, monospace;
font-size: 0.8125rem;
}
.mobile-form-select {
appearance: none;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
background-repeat: no-repeat;
background-position: right 0.75rem center;
padding-right: 2.5rem;
}
.mobile-form-checkbox-label {
display: flex;
align-items: center;
gap: var(--space-2);
font-weight: 500;
cursor: pointer;
}
.mobile-form-checkbox {
width: 22px;
height: 22px;
min-width: 22px;
min-height: 22px;
accent-color: var(--brand);
cursor: pointer;
}
.mobile-form-help {
display: block;
font-size: 0.8125rem;
color: var(--muted);
margin-top: var(--space-1);
}
.mobile-form-error {
display: flex;
align-items: center;
gap: var(--space-2);
padding: var(--space-3);
background: var(--danger-light);
color: var(--danger);
border-radius: 10px;
font-size: 0.875rem;
margin: var(--space-2) 0;
}
.mobile-discard-button {
width: 100%;
justify-content: center;
margin-top: var(--space-2);
}
/* Manifest editor responsive overrides inside mobile forms */
.mobile-manifest-editor-wrapper .card {
padding: var(--space-3);
margin-bottom: var(--space-3);
}
.mobile-manifest-editor-wrapper .row {
flex-direction: column;
align-items: stretch;
gap: var(--space-2);
}
.mobile-manifest-editor-wrapper .form-group,
.mobile-manifest-editor-wrapper input,
.mobile-manifest-editor-wrapper select,
.mobile-manifest-editor-wrapper textarea {
width: 100%;
min-width: 0;
}
.mobile-manifest-editor-wrapper input[style*="width:"] {
width: 100% !important;
}
/* Ensure minimum touch targets on mobile */ /* Ensure minimum touch targets on mobile */
button, button,
a, a,
@@ -0,0 +1,26 @@
# Mobile Tool Workshop & Profile UI Rework
## Goal
Completely rework the mobile experience for the **Tool Workshop** (tool configuration) and **Profile** (user profile configuration) pages so they are usable on small viewports while preserving all existing desktop functionality.
## Scope
- Rework `ToolWorkshopMobileView` to expose every field and action available on the desktop editor, including manifest definition support.
- Add a new `ProfileMobileView` and update `ProfilePage` to render it on small viewports.
- Keep desktop layouts and behavior unchanged.
- No backend API changes.
## Acceptance Criteria
- On viewports narrower than 768 px, the Tool Workshop page renders a mobile-friendly list/detail/edit flow.
- Tool type creation and editing on mobile support: definition type selection (Compose/Dockerfile/Manifest), manifest editor, interface type, conditional port, startup command, readiness probe, required variables, and all existing validation.
- On viewports narrower than 768 px, the Profile page renders a mobile-friendly form with avatar upload, name/email editing, validation, and save feedback.
- All existing desktop functionality remains intact.
- `npm run typecheck` and `npm run lint` pass in `apps/web`.
## Out of Scope
- Backend changes.
- Changes to other pages (sessions, settings, etc.).
- New features beyond the existing desktop capabilities.
@@ -0,0 +1,9 @@
# Tasks: Mobile Tool Workshop & Profile UI Rework
- [x] Inspect existing mobile components, desktop editors, and hooks.
- [x] Rework `ToolWorkshopMobileView` to support full desktop functionality (definition type, manifest editor, conditional port, startup command, readiness probe, required variables, validation).
- [x] Add responsive CSS for manifest editor and tool-workshop mobile forms.
- [x] Create `ProfileMobileView` component with mobile-friendly avatar/name/email form.
- [x] Update `ProfilePage` to render `ProfileMobileView` on mobile viewports.
- [x] Run `npm run typecheck` and `npm run lint` in `apps/web` and fix issues.
- [x] Update project maps for changed files.