2026-03-10 21:06:10 -05:00
|
|
|
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
2026-02-23 14:41:21 -06:00
|
|
|
import { useQuery } from "@tanstack/react-query";
|
2026-03-12 08:03:55 -05:00
|
|
|
import { BookOpen, Moon, Settings, Sun } from "lucide-react";
|
|
|
|
|
import { Link, Outlet, useLocation, useNavigate, useParams } from "@/lib/router";
|
2026-02-23 14:41:21 -06:00
|
|
|
import { CompanyRail } from "./CompanyRail";
|
2026-02-16 13:32:04 -06:00
|
|
|
import { Sidebar } from "./Sidebar";
|
2026-03-12 08:03:55 -05:00
|
|
|
import { InstanceSidebar } from "./InstanceSidebar";
|
Add shared UI primitives, contexts, and reusable components
Add shadcn components: avatar, breadcrumb, checkbox, collapsible,
command, dialog, dropdown-menu, label, popover, scroll-area, sheet,
skeleton, tabs, textarea, tooltip. Add shared components: BreadcrumbBar,
CommandPalette, CompanySwitcher, CommentThread, EmptyState, EntityRow,
FilterBar, InlineEditor, MetricCard, PageSkeleton, PriorityIcon,
PropertiesPanel, StatusIcon, SidebarNavItem/Section. Add contexts for
breadcrumbs, dialogs, and side panels. Add keyboard shortcut hook and
utility helpers. Update layout, sidebar, and main app shell.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 09:57:00 -06:00
|
|
|
import { BreadcrumbBar } from "./BreadcrumbBar";
|
|
|
|
|
import { PropertiesPanel } from "./PropertiesPanel";
|
|
|
|
|
import { CommandPalette } from "./CommandPalette";
|
|
|
|
|
import { NewIssueDialog } from "./NewIssueDialog";
|
2026-02-17 10:53:20 -06:00
|
|
|
import { NewProjectDialog } from "./NewProjectDialog";
|
2026-02-20 13:12:39 -06:00
|
|
|
import { NewGoalDialog } from "./NewGoalDialog";
|
Build out agent management UI: detail page, create dialog, list view
Add NewAgentDialog for creating agents with adapter config. Expand
AgentDetail page with tabbed view (overview, runs, config, logs),
run history timeline, and live status. Enhance Agents list page with
richer cards and filtering. Update AgentProperties panel, API client,
query keys, and utility helpers.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 12:33:04 -06:00
|
|
|
import { NewAgentDialog } from "./NewAgentDialog";
|
2026-02-20 13:47:13 -06:00
|
|
|
import { ToastViewport } from "./ToastViewport";
|
2026-02-23 16:08:24 -06:00
|
|
|
import { MobileBottomNav } from "./MobileBottomNav";
|
2026-03-13 11:12:43 -05:00
|
|
|
import { WorktreeBanner } from "./WorktreeBanner";
|
Add shared UI primitives, contexts, and reusable components
Add shadcn components: avatar, breadcrumb, checkbox, collapsible,
command, dialog, dropdown-menu, label, popover, scroll-area, sheet,
skeleton, tabs, textarea, tooltip. Add shared components: BreadcrumbBar,
CommandPalette, CompanySwitcher, CommentThread, EmptyState, EntityRow,
FilterBar, InlineEditor, MetricCard, PageSkeleton, PriorityIcon,
PropertiesPanel, StatusIcon, SidebarNavItem/Section. Add contexts for
breadcrumbs, dialogs, and side panels. Add keyboard shortcut hook and
utility helpers. Update layout, sidebar, and main app shell.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 09:57:00 -06:00
|
|
|
import { useDialog } from "../context/DialogContext";
|
|
|
|
|
import { usePanel } from "../context/PanelContext";
|
2026-02-17 13:24:33 -06:00
|
|
|
import { useCompany } from "../context/CompanyContext";
|
2026-02-20 10:32:32 -06:00
|
|
|
import { useSidebar } from "../context/SidebarContext";
|
2026-02-26 16:33:29 -06:00
|
|
|
import { useTheme } from "../context/ThemeContext";
|
Add shared UI primitives, contexts, and reusable components
Add shadcn components: avatar, breadcrumb, checkbox, collapsible,
command, dialog, dropdown-menu, label, popover, scroll-area, sheet,
skeleton, tabs, textarea, tooltip. Add shared components: BreadcrumbBar,
CommandPalette, CompanySwitcher, CommentThread, EmptyState, EntityRow,
FilterBar, InlineEditor, MetricCard, PageSkeleton, PriorityIcon,
PropertiesPanel, StatusIcon, SidebarNavItem/Section. Add contexts for
breadcrumbs, dialogs, and side panels. Add keyboard shortcut hook and
utility helpers. Update layout, sidebar, and main app shell.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 09:57:00 -06:00
|
|
|
import { useKeyboardShortcuts } from "../hooks/useKeyboardShortcuts";
|
2026-02-23 14:47:54 -06:00
|
|
|
import { useCompanyPageMemory } from "../hooks/useCompanyPageMemory";
|
2026-02-23 14:41:21 -06:00
|
|
|
import { healthApi } from "../api/health";
|
2026-03-12 16:04:28 -05:00
|
|
|
import { shouldSyncCompanySelectionFromRoute } from "../lib/company-selection";
|
2026-02-23 14:41:21 -06:00
|
|
|
import { queryKeys } from "../lib/queryKeys";
|
Add shared UI primitives, contexts, and reusable components
Add shadcn components: avatar, breadcrumb, checkbox, collapsible,
command, dialog, dropdown-menu, label, popover, scroll-area, sheet,
skeleton, tabs, textarea, tooltip. Add shared components: BreadcrumbBar,
CommandPalette, CompanySwitcher, CommentThread, EmptyState, EntityRow,
FilterBar, InlineEditor, MetricCard, PageSkeleton, PriorityIcon,
PropertiesPanel, StatusIcon, SidebarNavItem/Section. Add contexts for
breadcrumbs, dialogs, and side panels. Add keyboard shortcut hook and
utility helpers. Update layout, sidebar, and main app shell.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 09:57:00 -06:00
|
|
|
import { cn } from "../lib/utils";
|
2026-03-10 16:38:46 -05:00
|
|
|
import { NotFoundPage } from "../pages/NotFound";
|
2026-02-26 16:33:29 -06:00
|
|
|
import { Button } from "@/components/ui/button";
|
2026-02-16 13:32:04 -06:00
|
|
|
|
2026-03-13 16:22:34 -05:00
|
|
|
const INSTANCE_SETTINGS_MEMORY_KEY = "paperclip.lastInstanceSettingsPath";
|
|
|
|
|
const DEFAULT_INSTANCE_SETTINGS_PATH = "/instance/settings/heartbeats";
|
|
|
|
|
|
|
|
|
|
function normalizeRememberedInstanceSettingsPath(rawPath: string | null): string {
|
|
|
|
|
if (!rawPath) return DEFAULT_INSTANCE_SETTINGS_PATH;
|
|
|
|
|
|
|
|
|
|
const match = rawPath.match(/^([^?#]*)(\?[^#]*)?(#.*)?$/);
|
|
|
|
|
const pathname = match?.[1] ?? rawPath;
|
|
|
|
|
const search = match?.[2] ?? "";
|
|
|
|
|
const hash = match?.[3] ?? "";
|
|
|
|
|
|
|
|
|
|
if (pathname === "/instance/settings/heartbeats" || pathname === "/instance/settings/plugins") {
|
|
|
|
|
return `${pathname}${search}${hash}`;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (/^\/instance\/settings\/plugins\/[^/?#]+$/.test(pathname)) {
|
|
|
|
|
return `${pathname}${search}${hash}`;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return DEFAULT_INSTANCE_SETTINGS_PATH;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function readRememberedInstanceSettingsPath(): string {
|
|
|
|
|
if (typeof window === "undefined") return DEFAULT_INSTANCE_SETTINGS_PATH;
|
|
|
|
|
try {
|
|
|
|
|
return normalizeRememberedInstanceSettingsPath(window.localStorage.getItem(INSTANCE_SETTINGS_MEMORY_KEY));
|
|
|
|
|
} catch {
|
|
|
|
|
return DEFAULT_INSTANCE_SETTINGS_PATH;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-16 13:32:04 -06:00
|
|
|
export function Layout() {
|
2026-02-20 10:32:32 -06:00
|
|
|
const { sidebarOpen, setSidebarOpen, toggleSidebar, isMobile } = useSidebar();
|
2026-02-17 13:24:33 -06:00
|
|
|
const { openNewIssue, openOnboarding } = useDialog();
|
2026-03-03 12:07:01 -06:00
|
|
|
const { togglePanelVisible } = usePanel();
|
2026-03-10 16:38:46 -05:00
|
|
|
const {
|
|
|
|
|
companies,
|
|
|
|
|
loading: companiesLoading,
|
|
|
|
|
selectedCompany,
|
|
|
|
|
selectedCompanyId,
|
2026-03-12 16:04:28 -05:00
|
|
|
selectionSource,
|
2026-03-10 16:38:46 -05:00
|
|
|
setSelectedCompanyId,
|
|
|
|
|
} = useCompany();
|
2026-02-26 16:33:29 -06:00
|
|
|
const { theme, toggleTheme } = useTheme();
|
2026-03-02 16:44:03 -06:00
|
|
|
const { companyPrefix } = useParams<{ companyPrefix: string }>();
|
|
|
|
|
const navigate = useNavigate();
|
|
|
|
|
const location = useLocation();
|
2026-03-12 08:03:55 -05:00
|
|
|
const isInstanceSettingsRoute = location.pathname.startsWith("/instance/");
|
2026-02-17 13:24:33 -06:00
|
|
|
const onboardingTriggered = useRef(false);
|
2026-02-23 16:08:24 -06:00
|
|
|
const lastMainScrollTop = useRef(0);
|
|
|
|
|
const [mobileNavVisible, setMobileNavVisible] = useState(true);
|
2026-03-13 16:22:34 -05:00
|
|
|
const [instanceSettingsTarget, setInstanceSettingsTarget] = useState<string>(() => readRememberedInstanceSettingsPath());
|
2026-02-26 16:33:29 -06:00
|
|
|
const nextTheme = theme === "dark" ? "light" : "dark";
|
2026-03-10 16:38:46 -05:00
|
|
|
const matchedCompany = useMemo(() => {
|
|
|
|
|
if (!companyPrefix) return null;
|
|
|
|
|
const requestedPrefix = companyPrefix.toUpperCase();
|
|
|
|
|
return companies.find((company) => company.issuePrefix.toUpperCase() === requestedPrefix) ?? null;
|
|
|
|
|
}, [companies, companyPrefix]);
|
|
|
|
|
const hasUnknownCompanyPrefix =
|
|
|
|
|
Boolean(companyPrefix) && !companiesLoading && companies.length > 0 && !matchedCompany;
|
2026-02-23 14:41:21 -06:00
|
|
|
const { data: health } = useQuery({
|
|
|
|
|
queryKey: queryKeys.health,
|
|
|
|
|
queryFn: () => healthApi.get(),
|
|
|
|
|
retry: false,
|
|
|
|
|
});
|
2026-02-17 13:24:33 -06:00
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
if (companiesLoading || onboardingTriggered.current) return;
|
2026-02-23 14:41:21 -06:00
|
|
|
if (health?.deploymentMode === "authenticated") return;
|
2026-02-17 13:24:33 -06:00
|
|
|
if (companies.length === 0) {
|
|
|
|
|
onboardingTriggered.current = true;
|
|
|
|
|
openOnboarding();
|
|
|
|
|
}
|
2026-02-23 14:41:21 -06:00
|
|
|
}, [companies, companiesLoading, openOnboarding, health?.deploymentMode]);
|
Add shared UI primitives, contexts, and reusable components
Add shadcn components: avatar, breadcrumb, checkbox, collapsible,
command, dialog, dropdown-menu, label, popover, scroll-area, sheet,
skeleton, tabs, textarea, tooltip. Add shared components: BreadcrumbBar,
CommandPalette, CompanySwitcher, CommentThread, EmptyState, EntityRow,
FilterBar, InlineEditor, MetricCard, PageSkeleton, PriorityIcon,
PropertiesPanel, StatusIcon, SidebarNavItem/Section. Add contexts for
breadcrumbs, dialogs, and side panels. Add keyboard shortcut hook and
utility helpers. Update layout, sidebar, and main app shell.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 09:57:00 -06:00
|
|
|
|
2026-03-02 16:44:03 -06:00
|
|
|
useEffect(() => {
|
|
|
|
|
if (!companyPrefix || companiesLoading || companies.length === 0) return;
|
|
|
|
|
|
2026-03-10 16:38:46 -05:00
|
|
|
if (!matchedCompany) {
|
|
|
|
|
const fallback = (selectedCompanyId ? companies.find((company) => company.id === selectedCompanyId) : null)
|
|
|
|
|
?? companies[0]
|
|
|
|
|
?? null;
|
|
|
|
|
if (fallback && selectedCompanyId !== fallback.id) {
|
|
|
|
|
setSelectedCompanyId(fallback.id, { source: "route_sync" });
|
|
|
|
|
}
|
2026-03-02 16:44:03 -06:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-10 16:38:46 -05:00
|
|
|
if (companyPrefix !== matchedCompany.issuePrefix) {
|
2026-03-02 16:44:03 -06:00
|
|
|
const suffix = location.pathname.replace(/^\/[^/]+/, "");
|
2026-03-10 16:38:46 -05:00
|
|
|
navigate(`/${matchedCompany.issuePrefix}${suffix}${location.search}`, { replace: true });
|
2026-03-02 16:44:03 -06:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-12 16:04:28 -05:00
|
|
|
if (
|
|
|
|
|
shouldSyncCompanySelectionFromRoute({
|
|
|
|
|
selectionSource,
|
|
|
|
|
selectedCompanyId,
|
|
|
|
|
routeCompanyId: matchedCompany.id,
|
|
|
|
|
})
|
|
|
|
|
) {
|
2026-03-10 16:38:46 -05:00
|
|
|
setSelectedCompanyId(matchedCompany.id, { source: "route_sync" });
|
2026-03-02 16:44:03 -06:00
|
|
|
}
|
|
|
|
|
}, [
|
|
|
|
|
companyPrefix,
|
|
|
|
|
companies,
|
|
|
|
|
companiesLoading,
|
2026-03-10 16:38:46 -05:00
|
|
|
matchedCompany,
|
2026-03-02 16:44:03 -06:00
|
|
|
location.pathname,
|
|
|
|
|
location.search,
|
|
|
|
|
navigate,
|
2026-03-12 16:04:28 -05:00
|
|
|
selectionSource,
|
2026-03-02 16:44:03 -06:00
|
|
|
selectedCompanyId,
|
|
|
|
|
setSelectedCompanyId,
|
|
|
|
|
]);
|
|
|
|
|
|
2026-03-03 12:07:01 -06:00
|
|
|
const togglePanel = togglePanelVisible;
|
Add shared UI primitives, contexts, and reusable components
Add shadcn components: avatar, breadcrumb, checkbox, collapsible,
command, dialog, dropdown-menu, label, popover, scroll-area, sheet,
skeleton, tabs, textarea, tooltip. Add shared components: BreadcrumbBar,
CommandPalette, CompanySwitcher, CommentThread, EmptyState, EntityRow,
FilterBar, InlineEditor, MetricCard, PageSkeleton, PriorityIcon,
PropertiesPanel, StatusIcon, SidebarNavItem/Section. Add contexts for
breadcrumbs, dialogs, and side panels. Add keyboard shortcut hook and
utility helpers. Update layout, sidebar, and main app shell.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 09:57:00 -06:00
|
|
|
|
2026-02-23 14:47:54 -06:00
|
|
|
useCompanyPageMemory();
|
|
|
|
|
|
Add shared UI primitives, contexts, and reusable components
Add shadcn components: avatar, breadcrumb, checkbox, collapsible,
command, dialog, dropdown-menu, label, popover, scroll-area, sheet,
skeleton, tabs, textarea, tooltip. Add shared components: BreadcrumbBar,
CommandPalette, CompanySwitcher, CommentThread, EmptyState, EntityRow,
FilterBar, InlineEditor, MetricCard, PageSkeleton, PriorityIcon,
PropertiesPanel, StatusIcon, SidebarNavItem/Section. Add contexts for
breadcrumbs, dialogs, and side panels. Add keyboard shortcut hook and
utility helpers. Update layout, sidebar, and main app shell.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 09:57:00 -06:00
|
|
|
useKeyboardShortcuts({
|
|
|
|
|
onNewIssue: () => openNewIssue(),
|
|
|
|
|
onToggleSidebar: toggleSidebar,
|
|
|
|
|
onTogglePanel: togglePanel,
|
|
|
|
|
});
|
Overhaul UI with shadcn components and new pages
Add shadcn/ui components (badge, button, card, input, select,
separator). Add company context provider. New pages: Activity,
Approvals, Companies, Costs, Org chart. Restyle existing pages
(Dashboard, Agents, Issues, Goals, Projects) with shadcn components
and dark theme. Update layout, sidebar navigation, and routing.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 09:07:32 -06:00
|
|
|
|
2026-02-23 16:08:24 -06:00
|
|
|
useEffect(() => {
|
|
|
|
|
if (!isMobile) {
|
|
|
|
|
setMobileNavVisible(true);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
lastMainScrollTop.current = 0;
|
|
|
|
|
setMobileNavVisible(true);
|
|
|
|
|
}, [isMobile]);
|
|
|
|
|
|
2026-02-25 21:43:49 -06:00
|
|
|
// Swipe gesture to open/close sidebar on mobile
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
if (!isMobile) return;
|
|
|
|
|
|
|
|
|
|
const EDGE_ZONE = 30; // px from left edge to start open-swipe
|
|
|
|
|
const MIN_DISTANCE = 50; // minimum horizontal swipe distance
|
|
|
|
|
const MAX_VERTICAL = 75; // max vertical drift before we ignore
|
|
|
|
|
|
|
|
|
|
let startX = 0;
|
|
|
|
|
let startY = 0;
|
|
|
|
|
|
|
|
|
|
const onTouchStart = (e: TouchEvent) => {
|
|
|
|
|
const t = e.touches[0]!;
|
|
|
|
|
startX = t.clientX;
|
|
|
|
|
startY = t.clientY;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const onTouchEnd = (e: TouchEvent) => {
|
|
|
|
|
const t = e.changedTouches[0]!;
|
|
|
|
|
const dx = t.clientX - startX;
|
|
|
|
|
const dy = Math.abs(t.clientY - startY);
|
|
|
|
|
|
|
|
|
|
if (dy > MAX_VERTICAL) return; // vertical scroll, ignore
|
|
|
|
|
|
|
|
|
|
// Swipe right from left edge → open
|
|
|
|
|
if (!sidebarOpen && startX < EDGE_ZONE && dx > MIN_DISTANCE) {
|
|
|
|
|
setSidebarOpen(true);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Swipe left when open → close
|
|
|
|
|
if (sidebarOpen && dx < -MIN_DISTANCE) {
|
|
|
|
|
setSidebarOpen(false);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
document.addEventListener("touchstart", onTouchStart, { passive: true });
|
|
|
|
|
document.addEventListener("touchend", onTouchEnd, { passive: true });
|
|
|
|
|
|
|
|
|
|
return () => {
|
|
|
|
|
document.removeEventListener("touchstart", onTouchStart);
|
|
|
|
|
document.removeEventListener("touchend", onTouchEnd);
|
|
|
|
|
};
|
|
|
|
|
}, [isMobile, sidebarOpen, setSidebarOpen]);
|
|
|
|
|
|
2026-03-10 21:06:10 -05:00
|
|
|
const updateMobileNavVisibility = useCallback((currentTop: number) => {
|
|
|
|
|
const delta = currentTop - lastMainScrollTop.current;
|
2026-02-23 16:08:24 -06:00
|
|
|
|
2026-03-10 21:06:10 -05:00
|
|
|
if (currentTop <= 24) {
|
|
|
|
|
setMobileNavVisible(true);
|
|
|
|
|
} else if (delta > 8) {
|
|
|
|
|
setMobileNavVisible(false);
|
|
|
|
|
} else if (delta < -8) {
|
|
|
|
|
setMobileNavVisible(true);
|
|
|
|
|
}
|
2026-02-23 16:08:24 -06:00
|
|
|
|
2026-03-10 21:06:10 -05:00
|
|
|
lastMainScrollTop.current = currentTop;
|
|
|
|
|
}, []);
|
2026-02-23 16:08:24 -06:00
|
|
|
|
2026-03-10 21:06:10 -05:00
|
|
|
useEffect(() => {
|
|
|
|
|
if (!isMobile) {
|
|
|
|
|
setMobileNavVisible(true);
|
|
|
|
|
lastMainScrollTop.current = 0;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const onScroll = () => {
|
|
|
|
|
updateMobileNavVisibility(window.scrollY || document.documentElement.scrollTop || 0);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
onScroll();
|
|
|
|
|
window.addEventListener("scroll", onScroll, { passive: true });
|
|
|
|
|
|
|
|
|
|
return () => {
|
|
|
|
|
window.removeEventListener("scroll", onScroll);
|
|
|
|
|
};
|
|
|
|
|
}, [isMobile, updateMobileNavVisibility]);
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
const previousOverflow = document.body.style.overflow;
|
|
|
|
|
|
|
|
|
|
document.body.style.overflow = isMobile ? "visible" : "hidden";
|
|
|
|
|
|
|
|
|
|
return () => {
|
|
|
|
|
document.body.style.overflow = previousOverflow;
|
|
|
|
|
};
|
|
|
|
|
}, [isMobile]);
|
2026-02-23 16:08:24 -06:00
|
|
|
|
2026-03-13 16:22:34 -05:00
|
|
|
useEffect(() => {
|
|
|
|
|
if (!location.pathname.startsWith("/instance/settings/")) return;
|
|
|
|
|
|
|
|
|
|
const nextPath = normalizeRememberedInstanceSettingsPath(
|
|
|
|
|
`${location.pathname}${location.search}${location.hash}`,
|
|
|
|
|
);
|
|
|
|
|
setInstanceSettingsTarget(nextPath);
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
window.localStorage.setItem(INSTANCE_SETTINGS_MEMORY_KEY, nextPath);
|
|
|
|
|
} catch {
|
|
|
|
|
// Ignore storage failures in restricted environments.
|
|
|
|
|
}
|
|
|
|
|
}, [location.hash, location.pathname, location.search]);
|
|
|
|
|
|
2026-02-16 13:32:04 -06:00
|
|
|
return (
|
2026-03-10 21:06:10 -05:00
|
|
|
<div
|
|
|
|
|
className={cn(
|
|
|
|
|
"bg-background text-foreground pt-[env(safe-area-inset-top)]",
|
2026-03-13 11:12:43 -05:00
|
|
|
isMobile ? "min-h-dvh" : "flex h-dvh flex-col overflow-hidden",
|
2026-03-10 21:06:10 -05:00
|
|
|
)}
|
|
|
|
|
>
|
2026-03-02 16:44:03 -06:00
|
|
|
<a
|
|
|
|
|
href="#main-content"
|
|
|
|
|
className="sr-only focus:not-sr-only focus:fixed focus:left-3 focus:top-3 focus:z-[200] focus:rounded-md focus:bg-background focus:px-3 focus:py-2 focus:text-sm focus:font-medium focus:shadow-lg focus:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
|
|
|
|
>
|
|
|
|
|
Skip to Main Content
|
|
|
|
|
</a>
|
2026-03-13 11:12:43 -05:00
|
|
|
<WorktreeBanner />
|
|
|
|
|
<div className={cn("min-h-0 flex-1", isMobile ? "w-full" : "flex overflow-hidden")}>
|
|
|
|
|
{isMobile && sidebarOpen && (
|
|
|
|
|
<button
|
|
|
|
|
type="button"
|
|
|
|
|
className="fixed inset-0 z-40 bg-black/50"
|
|
|
|
|
onClick={() => setSidebarOpen(false)}
|
|
|
|
|
aria-label="Close sidebar"
|
|
|
|
|
/>
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
{isMobile ? (
|
|
|
|
|
<div
|
|
|
|
|
className={cn(
|
|
|
|
|
"fixed inset-y-0 left-0 z-50 flex flex-col overflow-hidden pt-[env(safe-area-inset-top)] transition-transform duration-100 ease-out",
|
|
|
|
|
sidebarOpen ? "translate-x-0" : "-translate-x-full"
|
|
|
|
|
)}
|
|
|
|
|
>
|
|
|
|
|
<div className="flex flex-1 min-h-0 overflow-hidden">
|
|
|
|
|
<CompanyRail />
|
2026-03-12 08:03:55 -05:00
|
|
|
{isInstanceSettingsRoute ? <InstanceSidebar /> : <Sidebar />}
|
2026-02-23 14:41:21 -06:00
|
|
|
</div>
|
2026-03-13 11:12:43 -05:00
|
|
|
<div className="border-t border-r border-border px-3 py-2 bg-background">
|
|
|
|
|
<div className="flex items-center gap-1">
|
|
|
|
|
<a
|
|
|
|
|
href="https://docs.paperclip.ing/"
|
|
|
|
|
target="_blank"
|
|
|
|
|
rel="noopener noreferrer"
|
|
|
|
|
className="flex items-center gap-2.5 px-3 py-2 text-[13px] font-medium transition-colors text-foreground/80 hover:bg-accent/50 hover:text-foreground flex-1 min-w-0"
|
|
|
|
|
>
|
|
|
|
|
<BookOpen className="h-4 w-4 shrink-0" />
|
|
|
|
|
<span className="truncate">Documentation</span>
|
|
|
|
|
</a>
|
|
|
|
|
<Button variant="ghost" size="icon-sm" className="text-muted-foreground shrink-0" asChild>
|
|
|
|
|
<Link
|
2026-03-13 16:22:34 -05:00
|
|
|
to={instanceSettingsTarget}
|
2026-03-13 11:12:43 -05:00
|
|
|
aria-label="Instance settings"
|
|
|
|
|
title="Instance settings"
|
|
|
|
|
onClick={() => {
|
|
|
|
|
if (isMobile) setSidebarOpen(false);
|
|
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
<Settings className="h-4 w-4" />
|
|
|
|
|
</Link>
|
|
|
|
|
</Button>
|
|
|
|
|
<Button
|
|
|
|
|
type="button"
|
|
|
|
|
variant="ghost"
|
|
|
|
|
size="icon-sm"
|
|
|
|
|
className="text-muted-foreground shrink-0"
|
|
|
|
|
onClick={toggleTheme}
|
|
|
|
|
aria-label={`Switch to ${nextTheme} mode`}
|
|
|
|
|
title={`Switch to ${nextTheme} mode`}
|
|
|
|
|
>
|
|
|
|
|
{theme === "dark" ? <Sun className="h-4 w-4" /> : <Moon className="h-4 w-4" />}
|
|
|
|
|
</Button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
2026-02-23 14:41:21 -06:00
|
|
|
</div>
|
2026-03-13 11:12:43 -05:00
|
|
|
) : (
|
|
|
|
|
<div className="flex h-full flex-col shrink-0">
|
|
|
|
|
<div className="flex flex-1 min-h-0">
|
|
|
|
|
<CompanyRail />
|
|
|
|
|
<div
|
|
|
|
|
className={cn(
|
|
|
|
|
"overflow-hidden transition-[width] duration-100 ease-out",
|
|
|
|
|
sidebarOpen ? "w-60" : "w-0"
|
|
|
|
|
)}
|
2026-03-12 14:39:50 -05:00
|
|
|
>
|
2026-03-13 11:12:43 -05:00
|
|
|
{isInstanceSettingsRoute ? <InstanceSidebar /> : <Sidebar />}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div className="border-t border-r border-border px-3 py-2">
|
|
|
|
|
<div className="flex items-center gap-1">
|
|
|
|
|
<a
|
|
|
|
|
href="https://docs.paperclip.ing/"
|
|
|
|
|
target="_blank"
|
|
|
|
|
rel="noopener noreferrer"
|
|
|
|
|
className="flex items-center gap-2.5 px-3 py-2 text-[13px] font-medium transition-colors text-foreground/80 hover:bg-accent/50 hover:text-foreground flex-1 min-w-0"
|
2026-03-12 08:03:55 -05:00
|
|
|
>
|
2026-03-13 11:12:43 -05:00
|
|
|
<BookOpen className="h-4 w-4 shrink-0" />
|
|
|
|
|
<span className="truncate">Documentation</span>
|
|
|
|
|
</a>
|
|
|
|
|
<Button variant="ghost" size="icon-sm" className="text-muted-foreground shrink-0" asChild>
|
|
|
|
|
<Link
|
2026-03-13 16:22:34 -05:00
|
|
|
to={instanceSettingsTarget}
|
2026-03-13 11:12:43 -05:00
|
|
|
aria-label="Instance settings"
|
|
|
|
|
title="Instance settings"
|
|
|
|
|
onClick={() => {
|
|
|
|
|
if (isMobile) setSidebarOpen(false);
|
|
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
<Settings className="h-4 w-4" />
|
|
|
|
|
</Link>
|
|
|
|
|
</Button>
|
|
|
|
|
<Button
|
|
|
|
|
type="button"
|
|
|
|
|
variant="ghost"
|
|
|
|
|
size="icon-sm"
|
|
|
|
|
className="text-muted-foreground shrink-0"
|
|
|
|
|
onClick={toggleTheme}
|
|
|
|
|
aria-label={`Switch to ${nextTheme} mode`}
|
|
|
|
|
title={`Switch to ${nextTheme} mode`}
|
|
|
|
|
>
|
|
|
|
|
{theme === "dark" ? <Sun className="h-4 w-4" /> : <Moon className="h-4 w-4" />}
|
|
|
|
|
</Button>
|
|
|
|
|
</div>
|
2026-02-26 16:33:29 -06:00
|
|
|
</div>
|
2026-02-23 14:41:21 -06:00
|
|
|
</div>
|
2026-03-13 11:12:43 -05:00
|
|
|
)}
|
2026-02-20 10:32:32 -06:00
|
|
|
|
2026-03-13 11:12:43 -05:00
|
|
|
<div className={cn("flex min-w-0 flex-col", isMobile ? "w-full" : "h-full flex-1")}>
|
|
|
|
|
<div
|
2026-03-10 21:06:10 -05:00
|
|
|
className={cn(
|
2026-03-13 11:12:43 -05:00
|
|
|
isMobile && "sticky top-0 z-20 bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/85",
|
2026-03-10 21:06:10 -05:00
|
|
|
)}
|
2026-02-23 16:08:24 -06:00
|
|
|
>
|
2026-03-13 11:12:43 -05:00
|
|
|
<BreadcrumbBar />
|
|
|
|
|
</div>
|
|
|
|
|
<div className={cn(isMobile ? "block" : "flex flex-1 min-h-0")}>
|
|
|
|
|
<main
|
|
|
|
|
id="main-content"
|
|
|
|
|
tabIndex={-1}
|
|
|
|
|
className={cn(
|
|
|
|
|
"flex-1 p-4 md:p-6",
|
|
|
|
|
isMobile ? "overflow-visible pb-[calc(5rem+env(safe-area-inset-bottom))]" : "overflow-auto",
|
|
|
|
|
)}
|
|
|
|
|
>
|
|
|
|
|
{hasUnknownCompanyPrefix ? (
|
|
|
|
|
<NotFoundPage
|
|
|
|
|
scope="invalid_company_prefix"
|
|
|
|
|
requestedPrefix={companyPrefix ?? selectedCompany?.issuePrefix}
|
|
|
|
|
/>
|
|
|
|
|
) : (
|
|
|
|
|
<Outlet />
|
|
|
|
|
)}
|
|
|
|
|
</main>
|
|
|
|
|
<PropertiesPanel />
|
|
|
|
|
</div>
|
Add shared UI primitives, contexts, and reusable components
Add shadcn components: avatar, breadcrumb, checkbox, collapsible,
command, dialog, dropdown-menu, label, popover, scroll-area, sheet,
skeleton, tabs, textarea, tooltip. Add shared components: BreadcrumbBar,
CommandPalette, CompanySwitcher, CommentThread, EmptyState, EntityRow,
FilterBar, InlineEditor, MetricCard, PageSkeleton, PriorityIcon,
PropertiesPanel, StatusIcon, SidebarNavItem/Section. Add contexts for
breadcrumbs, dialogs, and side panels. Add keyboard shortcut hook and
utility helpers. Update layout, sidebar, and main app shell.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 09:57:00 -06:00
|
|
|
</div>
|
Overhaul UI with shadcn components and new pages
Add shadcn/ui components (badge, button, card, input, select,
separator). Add company context provider. New pages: Activity,
Approvals, Companies, Costs, Org chart. Restyle existing pages
(Dashboard, Agents, Issues, Goals, Projects) with shadcn components
and dark theme. Update layout, sidebar navigation, and routing.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 09:07:32 -06:00
|
|
|
</div>
|
2026-02-23 16:08:24 -06:00
|
|
|
{isMobile && <MobileBottomNav visible={mobileNavVisible} />}
|
Add shared UI primitives, contexts, and reusable components
Add shadcn components: avatar, breadcrumb, checkbox, collapsible,
command, dialog, dropdown-menu, label, popover, scroll-area, sheet,
skeleton, tabs, textarea, tooltip. Add shared components: BreadcrumbBar,
CommandPalette, CompanySwitcher, CommentThread, EmptyState, EntityRow,
FilterBar, InlineEditor, MetricCard, PageSkeleton, PriorityIcon,
PropertiesPanel, StatusIcon, SidebarNavItem/Section. Add contexts for
breadcrumbs, dialogs, and side panels. Add keyboard shortcut hook and
utility helpers. Update layout, sidebar, and main app shell.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 09:57:00 -06:00
|
|
|
<CommandPalette />
|
|
|
|
|
<NewIssueDialog />
|
2026-02-17 10:53:20 -06:00
|
|
|
<NewProjectDialog />
|
2026-02-20 13:12:39 -06:00
|
|
|
<NewGoalDialog />
|
Build out agent management UI: detail page, create dialog, list view
Add NewAgentDialog for creating agents with adapter config. Expand
AgentDetail page with tabbed view (overview, runs, config, logs),
run history timeline, and live status. Enhance Agents list page with
richer cards and filtering. Update AgentProperties panel, API client,
query keys, and utility helpers.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 12:33:04 -06:00
|
|
|
<NewAgentDialog />
|
2026-02-20 13:47:13 -06:00
|
|
|
<ToastViewport />
|
2026-02-16 13:32:04 -06:00
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|