fix: make workspace project name visible on mobile and desktop cards
- Move the project name below the workspace title row so it reads as a distinct line with a project icon. - Move the status badge into the title row next to the workspace name, preventing it from crowding the project label. - Add .workspace-title-row flex styles and update .workspace-project-name to display inline-flex with a brand-colored project icon. Quality gates: npm run typecheck, npm run lint clean, npm test -- --run 87 passed.
This commit is contained in:
@@ -10,7 +10,7 @@ Entry point and core infrastructure for a React web application handling routing
|
||||
- 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
|
||||
## arch
|
||||
Modular React SPA with provider pattern for auth/session state, declarative routing with nested layouts and route guards, and centralized TypeScript type definitions for domain models.
|
||||
Provider composition pattern with React Router v6 (nested routes, layout routes, protected route guards), context-based auth/session management, and centralized TypeScript domain models.
|
||||
## tags
|
||||
pages, styles, css, router, react, session, dom, project
|
||||
## symbols
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
dir: apps/web/src/components
|
||||
|
||||
## role
|
||||
Provides reusable, accessible React UI components and utilities for the web application including layout shell, data states, icons, code display, authentication guards, and toast notifications.
|
||||
Provides reusable, accessible UI primitives and layout components for the web application, including navigation, feedback states, icons, code display, and authentication guards.
|
||||
## parent
|
||||
index: apps/web/src/.pi-map.index.md
|
||||
map: apps/web/src/.pi-map.md
|
||||
|
||||
@@ -4,9 +4,9 @@ dir: apps/web/src/components
|
||||
index: apps/web/src/components/.pi-map.index.md
|
||||
|
||||
## role
|
||||
Provides reusable, accessible React UI components and utilities for the web application including layout shell, data states, icons, code display, authentication guards, and toast notifications.
|
||||
Provides reusable, accessible UI primitives and layout components for the web application, including navigation, feedback states, icons, code display, and authentication guards.
|
||||
## files
|
||||
- app-shell.tsx | Renders the main application shell layout with navigation, session management, and responsive mobile/desktop views for a React Router-based application. | exp: AppShell | dep: react-router-dom, ../api/sessions, ../hooks/use-theme, ../state/auth, ../state/sessions, ../hooks/use-mobile-viewport, ../state/events, ../state/toast, ../state/notifications, ./features/notification/event-toast-bridge, ./features/notification/notification-center, ./icon, ./features/mobile/mobile-nav, ./features/tool/start-tool-fab, ../utils/icons
|
||||
- app-shell.tsx | Renders the main application shell layout with navigation, header, session management, and mobile-responsive behavior for a React Router-based SPA. | exp: AppShell | dep: react-router-dom, ../api/sessions, ../hooks/use-theme, ../state/auth, ../state/sessions, ../hooks/use-mobile-viewport, ../state/events, ../state/toast, ../state/notifications, ./features/notification/event-toast-bridge, ./features/notification/notification-center, ./icon, ./features/mobile/mobile-nav, ./features/tool/start-tool-fab, ../utils/icons
|
||||
- code-editor.tsx | A React component that renders a syntax-highlighted code editor with line numbers using react-simple-code-editor. | exp: CodeEditor | dep: react, react-simple-code-editor, ../utils/language
|
||||
- data-states.tsx | Provides reusable React components for displaying loading, error, and empty data states in a UI. | exp: LoadingState, ErrorState, EmptyState | dep: ./icon, icon
|
||||
- icon.tsx | Provides a centralized, type-safe React icon component that maps semantic names to Phosphor icons with configurable size, weight, color, and accessibility attributes. | exp: IconName, IconProps, Icon | dep: react, @phosphor-icons/react
|
||||
@@ -17,7 +17,7 @@ Provides reusable, accessible React UI components and utilities for the web appl
|
||||
- toast-rules.test.ts | Unit tests for mapping instance events to toast notification categories and severities | dep: vitest, ./toast-rules, ../types/events
|
||||
- toast-rules.ts | Maps instance events to toast notifications with deduplication logic to prevent spam | exp: func:mapEventToCategory(event: InstanceEventPayload) → string, call:event.event.startsWith, func:mapEventToSeverity(event: InstanceEventPayload) → "info" | "warning" | "error" | "success", func:handleEventToast(event: InstanceEventPayload) → void, call:shouldShowToast, call:toast.info, call:toast.success, call:toast.warning, call:toast.error, func:clearToastDedup() → void, call:lastToastTime.clear | dep: ../state/toast, ../types/events, toast state module, InstanceEventPayload type
|
||||
## arch
|
||||
Component-based architecture with functional React components, custom hooks for state management, centralized icon mapping with type safety, test-driven development for business logic (toast rules, protected routes), and separation of concerns between presentation (UI components) and behavior (utility modules).
|
||||
Component-based React architecture with functional components, composition patterns, separation of concerns (presentation vs logic), utility-first modules, and test co-location; integrates third-party libraries (Phosphor icons, react-simple-code-editor) with thin abstraction wrappers.
|
||||
## tags
|
||||
toast, icon, react, state, code, loading, event, editor
|
||||
## symbols
|
||||
|
||||
@@ -16,166 +16,166 @@ import { StartToolFAB } from "./features/tool/start-tool-fab";
|
||||
import type { IconName } from "../utils/icons";
|
||||
|
||||
const NAV_ITEMS: {
|
||||
to: string;
|
||||
label: string;
|
||||
icon: IconName;
|
||||
badge?: "sessions";
|
||||
to: string;
|
||||
label: string;
|
||||
icon: IconName;
|
||||
badge?: "sessions";
|
||||
}[] = [
|
||||
{ to: "/", label: "Home", icon: "dashboard" },
|
||||
{ to: "/projects", label: "Projects", icon: "projects" },
|
||||
{ to: "/workspaces", label: "Workspaces", icon: "folder" },
|
||||
{ to: "/sessions", label: "Sessions", icon: "terminal", badge: "sessions" },
|
||||
{ to: "/tool-workshop", label: "Tool Workshop", icon: "settings" },
|
||||
{ to: "/config-profiles", label: "Config Profiles", icon: "folder" },
|
||||
{ to: "/settings", label: "Settings", icon: "settings" },
|
||||
{ to: "/", label: "Home", icon: "dashboard" },
|
||||
{ to: "/projects", label: "Projects", icon: "projects" },
|
||||
{ to: "/workspaces", label: "Workspaces", icon: "folder" },
|
||||
{ to: "/sessions", label: "Sessions", icon: "terminal", badge: "sessions" },
|
||||
{ to: "/tool-workshop", label: "Tool Workshop", icon: "settings" },
|
||||
{ to: "/config-profiles", label: "Config Profiles", icon: "folder" },
|
||||
{ to: "/settings", label: "Settings", icon: "settings" },
|
||||
];
|
||||
|
||||
const SessionItem = ({ session }: { session: Session }) => {
|
||||
const isRunning = session.status === "running";
|
||||
const isRunning = session.status === "running";
|
||||
|
||||
// Determine the link target:
|
||||
// - Web tools open their tunnel URL
|
||||
// - Terminal tools open the terminal page
|
||||
// - Everything else falls back to the project page
|
||||
const hasTerminal = session.tool_type_interfaces.includes("terminal");
|
||||
const hasWeb = session.tool_type_interfaces.includes("web");
|
||||
const href =
|
||||
session.url && hasWeb
|
||||
? session.url
|
||||
: hasTerminal
|
||||
? `/instances/${session.id}/terminal`
|
||||
: `/projects/${session.project_id}`;
|
||||
// Determine the link target:
|
||||
// - Web tools open their tunnel URL
|
||||
// - Terminal tools open the terminal page
|
||||
// - Everything else falls back to the project page
|
||||
const hasTerminal = session.tool_type_interfaces.includes("terminal");
|
||||
const hasWeb = session.tool_type_interfaces.includes("web");
|
||||
const href =
|
||||
session.url && hasWeb
|
||||
? session.url
|
||||
: hasTerminal
|
||||
? `/instances/${session.id}/terminal`
|
||||
: `/projects/${session.project_id}`;
|
||||
|
||||
const tooltipParts = [session.display_name, session.project_name];
|
||||
if (session.workspace_name) tooltipParts.push(session.workspace_name);
|
||||
else if (session.repository_name) tooltipParts.push(session.repository_name);
|
||||
tooltipParts.push(`(${session.status})`);
|
||||
const tooltipParts = [session.display_name, session.project_name];
|
||||
if (session.workspace_name) tooltipParts.push(session.workspace_name);
|
||||
else if (session.repository_name) tooltipParts.push(session.repository_name);
|
||||
tooltipParts.push(`(${session.status})`);
|
||||
|
||||
return (
|
||||
<a
|
||||
href={href}
|
||||
target={`session-${session.id}`}
|
||||
rel="noreferrer"
|
||||
className="nav-item session-item"
|
||||
title={tooltipParts.join(" ")}
|
||||
>
|
||||
<span className={`session-status ${isRunning ? "running" : ""}`} />
|
||||
<Icon name={session.tool_icon as IconName} size="sm" />
|
||||
<span className="session-name">
|
||||
{session.display_name}
|
||||
<span className="session-tool">{session.tool_type_name}</span>
|
||||
</span>
|
||||
</a>
|
||||
);
|
||||
return (
|
||||
<a
|
||||
href={href}
|
||||
target={`session-${session.id}`}
|
||||
rel="noreferrer"
|
||||
className="nav-item session-item"
|
||||
title={tooltipParts.join(" ")}
|
||||
>
|
||||
<span className={`session-status ${isRunning ? "running" : ""}`} />
|
||||
<Icon name={session.tool_icon as IconName} size="sm" />
|
||||
<span className="session-name">
|
||||
{session.display_name}
|
||||
<span className="session-tool">{session.tool_type_name}</span>
|
||||
</span>
|
||||
</a>
|
||||
);
|
||||
};
|
||||
|
||||
export const AppShell = () => {
|
||||
useTheme();
|
||||
const { user, logout } = useAuth();
|
||||
const { sessions } = useSessions();
|
||||
const location = useLocation();
|
||||
const isMobile = useMobileViewport();
|
||||
const isMobileTerminal =
|
||||
isMobile &&
|
||||
location.pathname.includes("/instances/") &&
|
||||
location.pathname.includes("/terminal");
|
||||
useTheme();
|
||||
const { user, logout } = useAuth();
|
||||
const { sessions } = useSessions();
|
||||
const location = useLocation();
|
||||
const isMobile = useMobileViewport();
|
||||
const isMobileTerminal =
|
||||
isMobile &&
|
||||
location.pathname.includes("/instances/") &&
|
||||
location.pathname.includes("/terminal");
|
||||
|
||||
if (isMobileTerminal) {
|
||||
return (
|
||||
<EventProvider>
|
||||
<ToastProvider>
|
||||
<NotificationProvider>
|
||||
<EventToastBridge />
|
||||
<div className="shell mobile-terminal-shell">
|
||||
<Outlet />
|
||||
</div>
|
||||
</NotificationProvider>
|
||||
</ToastProvider>
|
||||
</EventProvider>
|
||||
);
|
||||
}
|
||||
if (isMobileTerminal) {
|
||||
return (
|
||||
<EventProvider>
|
||||
<ToastProvider>
|
||||
<NotificationProvider>
|
||||
<EventToastBridge />
|
||||
<div className="shell mobile-terminal-shell">
|
||||
<Outlet />
|
||||
</div>
|
||||
</NotificationProvider>
|
||||
</ToastProvider>
|
||||
</EventProvider>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<EventProvider>
|
||||
<ToastProvider>
|
||||
<NotificationProvider>
|
||||
<EventToastBridge />
|
||||
<div className="shell">
|
||||
<header className="shell-header">
|
||||
<Link className="brand" to="/">
|
||||
Headquarter
|
||||
</Link>
|
||||
<div className="header-actions">
|
||||
<NotificationCenter isMobileTerminal={isMobileTerminal} />
|
||||
<Link className="user-chip" to="/profile">
|
||||
{user?.name ?? "User"}
|
||||
</Link>
|
||||
<button
|
||||
className="ghost-button"
|
||||
onClick={() => {
|
||||
void logout();
|
||||
}}
|
||||
type="button"
|
||||
>
|
||||
<Icon name="logout" size="sm" />
|
||||
Logout
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
return (
|
||||
<EventProvider>
|
||||
<ToastProvider>
|
||||
<NotificationProvider>
|
||||
<EventToastBridge />
|
||||
<div className="shell">
|
||||
<header className="shell-header">
|
||||
<Link className="brand" to="/">
|
||||
Headquarter
|
||||
</Link>
|
||||
<div className="header-actions">
|
||||
<NotificationCenter isMobileTerminal={isMobileTerminal} />
|
||||
<Link className="user-chip" to="/profile">
|
||||
{user?.name ?? "User"}
|
||||
</Link>
|
||||
<button
|
||||
className="ghost-button"
|
||||
onClick={() => {
|
||||
void logout();
|
||||
}}
|
||||
type="button"
|
||||
>
|
||||
<Icon name="logout" size="sm" />
|
||||
Logout
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div className="shell-body">
|
||||
{!isMobile && (
|
||||
<aside className="shell-nav" aria-label="Primary navigation">
|
||||
{NAV_ITEMS.map((item) => {
|
||||
const activeCount = sessions.filter(
|
||||
(s) => s.status === "running",
|
||||
).length;
|
||||
return (
|
||||
<NavLink
|
||||
key={item.to}
|
||||
to={item.to}
|
||||
className={({ isActive }) =>
|
||||
isActive ? "nav-item nav-item-active" : "nav-item"
|
||||
}
|
||||
end={item.to === "/"}
|
||||
>
|
||||
<Icon name={item.icon} size="sm" />
|
||||
{item.label}
|
||||
{item.badge === "sessions" && activeCount > 0 && (
|
||||
<span className="nav-badge">{activeCount}</span>
|
||||
)}
|
||||
</NavLink>
|
||||
);
|
||||
})}
|
||||
<div className="shell-body">
|
||||
{!isMobile && (
|
||||
<aside className="shell-nav" aria-label="Primary navigation">
|
||||
{NAV_ITEMS.map((item) => {
|
||||
const activeCount = sessions.filter(
|
||||
(s) => s.status === "running",
|
||||
).length;
|
||||
return (
|
||||
<NavLink
|
||||
key={item.to}
|
||||
to={item.to}
|
||||
className={({ isActive }) =>
|
||||
isActive ? "nav-item nav-item-active" : "nav-item"
|
||||
}
|
||||
end={item.to === "/"}
|
||||
>
|
||||
<Icon name={item.icon} size="sm" />
|
||||
{item.label}
|
||||
{item.badge === "sessions" && activeCount > 0 && (
|
||||
<span className="nav-badge">{activeCount}</span>
|
||||
)}
|
||||
</NavLink>
|
||||
);
|
||||
})}
|
||||
|
||||
{sessions.length > 0 && (
|
||||
<>
|
||||
<div className="nav-divider" />
|
||||
<div className="nav-section-title">Live sessions</div>
|
||||
{sessions.map((session) => (
|
||||
<SessionItem key={session.id} session={session} />
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
</aside>
|
||||
)}
|
||||
{sessions.length > 0 && (
|
||||
<>
|
||||
<div className="nav-divider" />
|
||||
<div className="nav-section-title">Live sessions</div>
|
||||
{sessions.map((session) => (
|
||||
<SessionItem key={session.id} session={session} />
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
</aside>
|
||||
)}
|
||||
|
||||
<main className={`shell-content ${isMobile ? "mobile" : ""}`}>
|
||||
<Outlet />
|
||||
</main>
|
||||
</div>
|
||||
<main className={`shell-content ${isMobile ? "mobile" : ""}`}>
|
||||
<Outlet />
|
||||
</main>
|
||||
</div>
|
||||
|
||||
{isMobile && (
|
||||
<MobileNav
|
||||
sessionCount={
|
||||
sessions.filter((s) => s.status === "running").length
|
||||
}
|
||||
/>
|
||||
)}
|
||||
<StartToolFAB />
|
||||
</div>
|
||||
</NotificationProvider>
|
||||
</ToastProvider>
|
||||
</EventProvider>
|
||||
);
|
||||
{isMobile && (
|
||||
<MobileNav
|
||||
sessionCount={
|
||||
sessions.filter((s) => s.status === "running").length
|
||||
}
|
||||
/>
|
||||
)}
|
||||
<StartToolFAB />
|
||||
</div>
|
||||
</NotificationProvider>
|
||||
</ToastProvider>
|
||||
</EventProvider>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
dir: apps/web/src/components/features
|
||||
|
||||
## role
|
||||
Contains reusable React components organized by feature domains for the web application UI.
|
||||
Contains reusable React components that implement specific product features and business logic for the web application.
|
||||
## parent
|
||||
index: apps/web/src/components/.pi-map.index.md
|
||||
map: apps/web/src/components/.pi-map.md
|
||||
|
||||
@@ -4,10 +4,10 @@ dir: apps/web/src/components/features
|
||||
index: apps/web/src/components/features/.pi-map.index.md
|
||||
|
||||
## role
|
||||
Contains reusable React components organized by feature domains for the web application UI.
|
||||
Contains reusable React components that implement specific product features and business logic for the web application.
|
||||
## files
|
||||
## arch
|
||||
Feature-based component colocation with domain-driven folder structure, separating UI concerns by business capability rather than technical layer.
|
||||
Feature-based component organization following domain-driven design principles, likely with co-located components, hooks, and utilities per feature area.
|
||||
## tags
|
||||
-
|
||||
## symbols
|
||||
|
||||
@@ -6,114 +6,114 @@ import { SpacesBottomSheet } from "./spaces-bottom-sheet";
|
||||
import type { IconName } from "../../../utils/icons";
|
||||
|
||||
interface MobileNavProps {
|
||||
sessionCount?: number;
|
||||
sessionCount?: number;
|
||||
}
|
||||
|
||||
interface NavItem {
|
||||
to: string;
|
||||
label: string;
|
||||
icon: IconName;
|
||||
isGroup?: boolean;
|
||||
group?: "tools" | "spaces";
|
||||
to: string;
|
||||
label: string;
|
||||
icon: IconName;
|
||||
isGroup?: boolean;
|
||||
group?: "tools" | "spaces";
|
||||
}
|
||||
|
||||
const MOBILE_NAV_ITEMS: NavItem[] = [
|
||||
{ to: "/", label: "Home", icon: "dashboard" },
|
||||
{
|
||||
to: "/spaces",
|
||||
label: "Spaces",
|
||||
icon: "projects",
|
||||
isGroup: true,
|
||||
group: "spaces",
|
||||
},
|
||||
{ to: "/sessions", label: "Sessions", icon: "terminal" },
|
||||
{
|
||||
to: "/tools",
|
||||
label: "Tools",
|
||||
icon: "settings",
|
||||
isGroup: true,
|
||||
group: "tools",
|
||||
},
|
||||
{ to: "/settings", label: "Settings", icon: "settings" },
|
||||
{ to: "/", label: "Home", icon: "dashboard" },
|
||||
{
|
||||
to: "/spaces",
|
||||
label: "Spaces",
|
||||
icon: "projects",
|
||||
isGroup: true,
|
||||
group: "spaces",
|
||||
},
|
||||
{ to: "/sessions", label: "Sessions", icon: "terminal" },
|
||||
{
|
||||
to: "/tools",
|
||||
label: "Tools",
|
||||
icon: "settings",
|
||||
isGroup: true,
|
||||
group: "tools",
|
||||
},
|
||||
{ to: "/settings", label: "Settings", icon: "settings" },
|
||||
];
|
||||
|
||||
export const MobileNav: React.FC<MobileNavProps> = ({ sessionCount }) => {
|
||||
const location = useLocation();
|
||||
const [toolsSheetOpen, setToolsSheetOpen] = useState(false);
|
||||
const [spacesSheetOpen, setSpacesSheetOpen] = useState(false);
|
||||
const location = useLocation();
|
||||
const [toolsSheetOpen, setToolsSheetOpen] = useState(false);
|
||||
const [spacesSheetOpen, setSpacesSheetOpen] = useState(false);
|
||||
|
||||
const isToolsActive =
|
||||
location.pathname === "/tool-workshop" ||
|
||||
location.pathname === "/config-profiles";
|
||||
const isToolsActive =
|
||||
location.pathname === "/tool-workshop" ||
|
||||
location.pathname === "/config-profiles";
|
||||
|
||||
const isSpacesActive =
|
||||
location.pathname.startsWith("/projects") ||
|
||||
location.pathname.startsWith("/workspaces");
|
||||
const isSpacesActive =
|
||||
location.pathname.startsWith("/projects") ||
|
||||
location.pathname.startsWith("/workspaces");
|
||||
|
||||
const handleNavClick = (item: NavItem) => {
|
||||
if (!item.isGroup) return;
|
||||
if (item.group === "spaces") {
|
||||
setSpacesSheetOpen(true);
|
||||
} else {
|
||||
setToolsSheetOpen(true);
|
||||
}
|
||||
};
|
||||
const handleNavClick = (item: NavItem) => {
|
||||
if (!item.isGroup) return;
|
||||
if (item.group === "spaces") {
|
||||
setSpacesSheetOpen(true);
|
||||
} else {
|
||||
setToolsSheetOpen(true);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<nav
|
||||
className="mobile-nav"
|
||||
role="navigation"
|
||||
aria-label="Mobile navigation"
|
||||
>
|
||||
{MOBILE_NAV_ITEMS.map((item) => {
|
||||
if (item.isGroup) {
|
||||
const isActive =
|
||||
item.group === "spaces" ? isSpacesActive : isToolsActive;
|
||||
return (
|
||||
<button
|
||||
key={item.to}
|
||||
className={`mobile-nav-item ${isActive ? "active" : ""}`}
|
||||
onClick={() => handleNavClick(item)}
|
||||
type="button"
|
||||
>
|
||||
<div className="mobile-nav-icon-wrapper">
|
||||
<Icon name={item.icon} size="md" />
|
||||
</div>
|
||||
<span className="mobile-nav-label">{item.label}</span>
|
||||
</button>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<nav
|
||||
className="mobile-nav"
|
||||
role="navigation"
|
||||
aria-label="Mobile navigation"
|
||||
>
|
||||
{MOBILE_NAV_ITEMS.map((item) => {
|
||||
if (item.isGroup) {
|
||||
const isActive =
|
||||
item.group === "spaces" ? isSpacesActive : isToolsActive;
|
||||
return (
|
||||
<button
|
||||
key={item.to}
|
||||
className={`mobile-nav-item ${isActive ? "active" : ""}`}
|
||||
onClick={() => handleNavClick(item)}
|
||||
type="button"
|
||||
>
|
||||
<div className="mobile-nav-icon-wrapper">
|
||||
<Icon name={item.icon} size="md" />
|
||||
</div>
|
||||
<span className="mobile-nav-label">{item.label}</span>
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<NavLink
|
||||
key={item.to}
|
||||
to={item.to}
|
||||
className={({ isActive }) =>
|
||||
`mobile-nav-item ${isActive ? "active" : ""}`
|
||||
}
|
||||
end={item.to === "/"}
|
||||
>
|
||||
<div className="mobile-nav-icon-wrapper">
|
||||
<Icon name={item.icon} size="md" />
|
||||
{item.to === "/sessions" && sessionCount ? (
|
||||
<span className="mobile-nav-badge">{sessionCount}</span>
|
||||
) : null}
|
||||
</div>
|
||||
<span className="mobile-nav-label">{item.label}</span>
|
||||
</NavLink>
|
||||
);
|
||||
})}
|
||||
</nav>
|
||||
return (
|
||||
<NavLink
|
||||
key={item.to}
|
||||
to={item.to}
|
||||
className={({ isActive }) =>
|
||||
`mobile-nav-item ${isActive ? "active" : ""}`
|
||||
}
|
||||
end={item.to === "/"}
|
||||
>
|
||||
<div className="mobile-nav-icon-wrapper">
|
||||
<Icon name={item.icon} size="md" />
|
||||
{item.to === "/sessions" && sessionCount ? (
|
||||
<span className="mobile-nav-badge">{sessionCount}</span>
|
||||
) : null}
|
||||
</div>
|
||||
<span className="mobile-nav-label">{item.label}</span>
|
||||
</NavLink>
|
||||
);
|
||||
})}
|
||||
</nav>
|
||||
|
||||
<ToolsBottomSheet
|
||||
isOpen={toolsSheetOpen}
|
||||
onClose={() => setToolsSheetOpen(false)}
|
||||
/>
|
||||
<SpacesBottomSheet
|
||||
isOpen={spacesSheetOpen}
|
||||
onClose={() => setSpacesSheetOpen(false)}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
<ToolsBottomSheet
|
||||
isOpen={toolsSheetOpen}
|
||||
onClose={() => setToolsSheetOpen(false)}
|
||||
/>
|
||||
<SpacesBottomSheet
|
||||
isOpen={spacesSheetOpen}
|
||||
onClose={() => setSpacesSheetOpen(false)}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
dir: apps/web/src/components/features/workspace
|
||||
|
||||
## role
|
||||
Provides React UI components for workspace management, navigation, and detail views in a web-based development environment.
|
||||
Provides the complete UI component suite for workspace management, including creation, detail viewing, file editing, Git operations, tool management, and settings.
|
||||
## parent
|
||||
index: apps/web/src/components/features/.pi-map.index.md
|
||||
map: apps/web/src/components/features/.pi-map.md
|
||||
|
||||
@@ -4,9 +4,9 @@ dir: apps/web/src/components/features/workspace
|
||||
index: apps/web/src/components/features/workspace/.pi-map.index.md
|
||||
|
||||
## role
|
||||
Provides React UI components for workspace management, navigation, and detail views in a web-based development environment.
|
||||
Provides the complete UI component suite for workspace management, including creation, detail viewing, file editing, Git operations, tool management, and settings.
|
||||
## files
|
||||
- workspace-card.tsx | React card component that displays workspace information with status, metadata, and action buttons | exp: WorkspaceCardProps, func:WorkspaceCard({ workspace, loading = false, onStartTool, onSync, onDelete, }: WorkspaceCardProps), call:onStartTool, call:onSync, call:onDelete | dep: react-router-dom, ../../icon, ./workspace-instance-chips, ../../../types/workspace
|
||||
- workspace-card.tsx | React component that renders a card displaying workspace information with status, metadata, and action buttons. | exp: WorkspaceCardProps, func:WorkspaceCard({ workspace, loading = false, onStartTool, onSync, onDelete, }: WorkspaceCardProps), call:onStartTool, call:onSync, call:onDelete | dep: react-router-dom, ../../icon, ./workspace-instance-chips, ../../../types/workspace
|
||||
- workspace-create-form.tsx | React form component for creating workspaces with cascading project/repository/branch selectors and support for both contextual and standalone modes | exp: WorkspaceCreateFormProps, func:WorkspaceCreateForm({ onSubmit, onCancel, defaultProjectId, defaultRepoId, }: WorkspaceCreateFormProps), call:Boolean, call:useState, call:useGitRepo, call:useEffect, call:git.branches.includes, call:setSelectedBranch, call:setIsNewBranch, call:useCallback, call:listProjects, call:setProjects, call:setSelectedProject, call:setError, call:setFetchingProjects, call:loadProjects, call:setRepos, call:setSelectedRepo, call:listRepositories, call:loadRepos, call:setNewBranchName, call:e.preventDefault, call:name.trim, call:newBranchName.trim, call:setSubmitting, call:createWorkspaceTopLevel, call:onSubmit, call:projects.map, call:repos.map, call:setName, call:handleBranchChange, call:git.branches.map | dep: react, ../../icon, ../../../api/projects, ../../../api/git-repositories, ../../../api/workspaces, ../../../hooks/use-git-repo, ../../../types, icon, api/projects, api/git-repositories, api/workspaces, hooks/use-git-repo, types
|
||||
- workspace-detail-header.tsx | Renders a header component for a workspace detail page showing breadcrumb navigation and branch information. | exp: WorkspaceDetailHeaderProps, func:WorkspaceDetailHeader({ workspace, }: WorkspaceDetailHeaderProps) | dep: ../../icon, react
|
||||
- workspace-file-panel.tsx | Renders a file browser and editor panel with Git integration for a workspace detail page. | exp: func:WorkspaceFilePanel({ workspaceId }: WorkspaceFilePanelProps), call:useWorkspaceFiles, call:useWorkspaceGit, call:useState, call:setSelectedPath, call:setIsEditing, call:setEditContent, call:navigateTo, call:loadFile, call:currentPath.split("/").slice(0, -1).join, call:saveFile, call:setCommitMessage, call:commit, call:entries.map, call:handleSelect | dep: react, ../../icon, ../../../hooks/use-workspace-files, ../../../hooks/use-workspace-git, ../../../api/workspace-files
|
||||
@@ -17,7 +17,7 @@ Provides React UI components for workspace management, navigation, and detail vi
|
||||
- workspace-tab-bar.tsx | Renders desktop and mobile tab bar components for workspace navigation with files, git, tools, and settings tabs. | exp: WorkspaceTab, func:WorkspaceTabBar({ active, onChange }: WorkspaceTabBarProps), call:TABS.map, call:onChange, func:WorkspaceMobileTabBar({ active, onChange, }: WorkspaceTabBarProps), call:TABS.map, call:onChange | dep: ../../icon, react
|
||||
- workspace-tools-panel.tsx | Renders a tools management panel for a workspace that displays running tool instances and allows starting new tools via a modal | exp: func:WorkspaceToolsPanel({ workspace }: WorkspaceToolsPanelProps), call:useWorkspaceInstances, call:useState, call:setShowModal, call:instances.map, call:e.stopPropagation, call:refresh | dep: react, ../../icon, ../tool/tool-starter, ../../../hooks/use-workspace-instances, ../../../types/workspace, icon, tool-starter, use-workspace-instances, workspace types
|
||||
## arch
|
||||
Feature-based component organization with page-specific composite components (header/detail/settings) and reusable atomic pieces (cards, chips, forms), following a panel/tab architecture for workspace detail layout.
|
||||
Feature-based component organization with compound page pattern (header/tab-bar/panels), form abstraction for creation, and modal-driven tool management with responsive mobile/desktop tab navigation.
|
||||
## tags
|
||||
workspace, call:set, call:use, panel, git, branch, react, call:on
|
||||
## symbols
|
||||
|
||||
@@ -35,15 +35,18 @@ export function WorkspaceCard({
|
||||
className="workspace-title-link"
|
||||
>
|
||||
<div className="workspace-title-stack">
|
||||
<div className="workspace-title-row">
|
||||
<h4 className="workspace-name">{workspace.name}</h4>
|
||||
<span className={`status-badge ${statusClass}`}>
|
||||
{workspace.status}
|
||||
</span>
|
||||
</div>
|
||||
<span className="workspace-project-name">
|
||||
<Icon name="projects" size="sm" />
|
||||
{workspace.project_name}
|
||||
</span>
|
||||
<h4 className="workspace-name">{workspace.name}</h4>
|
||||
</div>
|
||||
</Link>
|
||||
<span className={`status-badge ${statusClass}`}>
|
||||
{workspace.status}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="workspace-card-body">
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
dir: apps/web/src/styles
|
||||
|
||||
## role
|
||||
Houses the complete CSS styling system for a web application, including design tokens, global styles, syntax highlighting themes, and utility classes.
|
||||
Provides the complete visual design system and styling foundation for a web application, including themes, utilities, and component-specific styles.
|
||||
## parent
|
||||
index: apps/web/src/.pi-map.index.md
|
||||
map: apps/web/src/.pi-map.md
|
||||
|
||||
@@ -4,14 +4,14 @@ dir: apps/web/src/styles
|
||||
index: apps/web/src/styles/.pi-map.index.md
|
||||
|
||||
## role
|
||||
Houses the complete CSS styling system for a web application, including design tokens, global styles, syntax highlighting themes, and utility classes.
|
||||
Provides the complete visual design system and styling foundation for a web application, including themes, utilities, and component-specific styles.
|
||||
## 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)
|
||||
- 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.
|
||||
- utilities.css | Provides a comprehensive responsive CSS utility system with terminal-specific styling, mobile adaptations, and component patterns for a web application. | dep: CSS custom properties (var(--space-*), var(--border), var(--bg), var(--text), var(--brand), var(--muted), var(--success), var(--danger), var(--danger-light)), xterm.js (terminal integration)
|
||||
## arch
|
||||
Token-driven CSS architecture using CSS custom properties for themeable light/dark modes, utility-first responsive patterns, and component-scoped stylesheets with Prism.js integration for syntax highlighting.
|
||||
Token-based CSS architecture using CSS custom properties for light/dark theming, with layered stylesheets separating tokens, global styles, utilities, and component-specific styling, supporting responsive and fluid design patterns.
|
||||
## tags
|
||||
var(, space, global, css, web, application, syntax, defines
|
||||
## symbols
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
dir: apps/web/src/styles/pages
|
||||
|
||||
## role
|
||||
Package containing page-specific CSS stylesheets for individual web application views
|
||||
Contains page-specific CSS stylesheets for individual application views in the web frontend, providing scoped styling for distinct feature areas.
|
||||
## parent
|
||||
index: apps/web/src/styles/.pi-map.index.md
|
||||
map: apps/web/src/styles/.pi-map.md
|
||||
|
||||
@@ -4,18 +4,18 @@ dir: apps/web/src/styles/pages
|
||||
index: apps/web/src/styles/pages/.pi-map.index.md
|
||||
|
||||
## role
|
||||
Package containing page-specific CSS stylesheets for individual web application views
|
||||
Contains page-specific CSS stylesheets for individual application views in the web frontend, providing scoped styling for distinct feature areas.
|
||||
## files
|
||||
- git-history.css | Stylesheet for a Git history visualization page with commit list, branch selector, and detail panel layout | dep: CSS custom properties (var(--border), var(--panel), var(--ink), var(--brand), var(--muted))
|
||||
- projects.css | Stylesheet for a projects page UI featuring expandable project toggles, repository listings, workspace grids, and repository creation radio controls. | dep: CSS custom properties (variables)
|
||||
- sessions.css | Stylesheet for session management UI components including cards, dropdowns, forms, and responsive mobile layouts. | dep: CSS custom properties/variables (var(--border), var(--panel), var(--text), var(--space-*), var(--danger), var(--color-border))
|
||||
- ssh-keys.css | Styles a responsive SSH key list component that stacks vertically on mobile and switches to horizontal layout on larger screens | dep: CSS custom properties (var(--space-3), var(--space-2), var(--space-4), var(--bg), var(--border))
|
||||
- workspace-detail.css | Stylesheet for a workspace detail page with tabbed interface including files, git, tools, and settings views, plus a workspace creation form | dep: CSS custom properties (variables for colors, spacing, typography)
|
||||
- workspaces.css | Styles a workspaces page with responsive card grid layout, status badges, and instance chips | dep: CSS custom properties (var(--space-*), var(--border), var(--brand), var(--panel), var(--ink), var(--muted), var(--bg), var(--success), var(--warning), var(--danger), var(--font-size-*))
|
||||
- workspaces.css | Stylesheet for a workspaces management page featuring responsive grid layout, workspace cards with status badges, instance chips, and mobile adaptations. | dep: CSS custom properties (design tokens: --space-*, --border, --panel, --brand, --ink, --muted, --bg, --success, --warning, --danger, --font-size-*)
|
||||
## arch
|
||||
Modular CSS architecture with dedicated stylesheets per page domain, featuring responsive design patterns, component-based styling, and mobile-first breakpoint strategies
|
||||
Modular CSS architecture with page-level style separation, responsive design patterns using media queries for mobile adaptation, and component-scoped styling organized by feature domain rather than shared component library.
|
||||
## tags
|
||||
var(, workspace, space, stylesheet, git, page, detail, history
|
||||
var(, workspace, stylesheet, space, git, page, detail, history
|
||||
## symbols
|
||||
-
|
||||
## workflows
|
||||
|
||||
@@ -40,7 +40,6 @@
|
||||
.workspace-card-top {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
gap: var(--space-3);
|
||||
}
|
||||
|
||||
@@ -63,15 +62,11 @@
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.workspace-project-name {
|
||||
font-size: var(--font-size-sm);
|
||||
font-weight: 500;
|
||||
color: var(--muted);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.04em;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
.workspace-title-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-2);
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.workspace-name {
|
||||
@@ -82,6 +77,24 @@
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.workspace-project-name {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: var(--space-1);
|
||||
font-size: var(--font-size-sm);
|
||||
font-weight: 500;
|
||||
color: var(--muted);
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.workspace-project-name .icon {
|
||||
color: var(--brand);
|
||||
}
|
||||
|
||||
/* Body: meta row + instance chips */
|
||||
|
||||
Reference in New Issue
Block a user