import { lazy, Suspense, type ReactNode } from "react"; import type { ToolConnectionCredentialSource } from "@paperclipai/shared"; import { Navigate, Outlet, Route, Routes, useActiveCompanyPrefix, useLocation, useParams } from "@/lib/router"; import { Button } from "@/components/ui/button"; import { useTranslation } from "@/i18n"; import { Layout } from "./components/Layout"; import { Layout as ProductionLayout } from "./components/Layout.production"; import { ConferenceRoomChatGate } from "./components/ConferenceRoomChatGate"; import { TaskChatLab } from "./pages/TaskChatLab"; import { PipelinesExperimentalGate } from "./components/PipelinesExperimentalGate"; import { CasesExperimentalGate } from "./components/CasesExperimentalGate"; import { StatusCardsExperimentalGate } from "./components/StatusCardsExperimentalGate"; import { CloudManagedPageGate } from "./components/CloudManagedPageGate"; import { HiddenSettingsPageGate } from "./components/HiddenSettingsPageGate"; import { IsolatedWorkspacesRouteGate } from "./components/IsolatedWorkspacesRouteGate"; import { ExecutionWorkspaceCompanyGate, UnprefixedExecutionWorkspaceRedirect, } from "./components/UnprefixedExecutionWorkspaceRedirect"; import { useHiddenSettings } from "./hooks/useHiddenSettings"; import { Cases } from "./pages/Cases"; import { CaseDetail } from "./pages/CaseDetail"; import { OnboardingWizardVariant } from "./components/OnboardingWizardVariant"; import { CloudAccessGate } from "./components/CloudAccessGate"; import { PaperclipLoading } from "./components/AnimatedPaperclipIcon"; import { Dashboard } from "./pages/Dashboard"; import { DashboardLive } from "./pages/DashboardLive"; import { Timeline } from "./pages/Timeline"; import { Companies } from "./pages/Companies"; import { AGENT_FILTER_TABS, Agents } from "./pages/Agents"; import { AgentDetail } from "./pages/AgentDetail"; import { Projects } from "./pages/Projects"; import { ProjectDetail } from "./pages/ProjectDetail"; import { ProjectWorkspaceDetail } from "./pages/ProjectWorkspaceDetail"; import { Workspaces } from "./pages/Workspaces"; import { Issues } from "./pages/Issues"; import { Search } from "./pages/Search"; import { IssueDetail } from "./pages/IssueDetail"; import { IssueChatLongThreadPerf } from "./pages/IssueChatLongThreadPerf"; import { Routines } from "./pages/Routines"; import { Learnings, PipelineItemDetail, PipelineItemLegacyRedirect, Pipelines, ReviewQueue } from "./pages/Pipelines"; import { PipelineSettings } from "./pages/PipelineSettings"; import { StatusCards } from "./pages/StatusCards"; import { RoutineDetail } from "./pages/RoutineDetail"; import { UserProfile } from "./pages/UserProfile"; import { ExecutionWorkspaceDetail } from "./pages/ExecutionWorkspaceDetail"; import { Goals } from "./pages/Goals"; import { Artifacts } from "./pages/Artifacts"; import { GoalDetail } from "./pages/GoalDetail"; import { Approvals } from "./pages/Approvals"; import { ApprovalDetail } from "./pages/ApprovalDetail"; import { CompanyActivity } from "./pages/audit/CompanyActivity"; import { AuditHub } from "./pages/audit/AuditHub"; import { Inbox } from "./pages/Inbox"; import { WhatNeedsMe } from "./pages/WhatNeedsMe"; import { DecisionQueuePage } from "./pages/DecisionQueuePage"; import { BoardChat } from "./pages/BoardChat"; import { CompanySettings } from "./pages/CompanySettings"; import { CompanyEnvironments } from "./pages/CompanyEnvironments"; import { BootstrapSetupUxLab } from "./pages/BootstrapSetupUxLab"; import { ResponsibleUserDenialUxLab } from "./pages/ResponsibleUserDenialUxLab"; import { CrossIssueCollaborationUxLab } from "./pages/CrossIssueCollaborationUxLab"; import { CompanySettingsPluginPage } from "./pages/CompanySettingsPluginPage"; import { CompanyAccess, CompanyAccessLegacyRoute } from "./pages/CompanyAccess"; import { AdvancedToolsRoute } from "./pages/tools/AdvancedToolsRoute"; import { ProfileWizardRoute } from "./pages/tools/profiles/ProfileWizardRoute"; import { ProfileDetailRoute } from "./pages/tools/profiles/ProfileDetailRoute"; import { Browse } from "./pages/apps/Browse"; import { AppsConnect } from "./pages/apps/AppsConnect"; import { canEnterAppsConnect } from "./pages/apps/app-connect-policy"; import { AppsReview } from "./pages/apps/AppsReview"; import { AppDetail } from "./pages/apps/AppDetail"; import { AppNotConnected } from "./pages/apps/AppNotConnected"; import { GatewaysList } from "./pages/apps/gateways/GatewaysList"; import { GatewayDetail } from "./pages/apps/gateways/GatewayDetail"; import { CompanySkills } from "./pages/CompanySkills"; import { SkillStudio } from "./pages/SkillStudio"; import { Secrets } from "./pages/Secrets"; import { CompanyImport } from "./pages/CompanyImport"; import { DesignGuide } from "./pages/DesignGuide"; import { InstanceExperimentalSettings } from "./pages/InstanceExperimentalSettings"; import { InstanceAccess } from "./pages/InstanceAccess"; import { ProfileSettings } from "./pages/ProfileSettings"; import { PluginManager } from "./pages/PluginManager"; import { PluginSettings } from "./pages/PluginSettings"; import { AdapterManager } from "./pages/AdapterManager"; import { PluginPage } from "./pages/PluginPage"; import { NewAgent } from "./pages/NewAgent"; import { AuthPage } from "./pages/Auth"; import { BoardClaimPage } from "./pages/BoardClaim"; import { CliAuthPage } from "./pages/CliAuth"; import { InviteLandingPage } from "./pages/InviteLanding"; import { JoinRequestQueue } from "./pages/JoinRequestQueue"; import { NotFoundPage } from "./pages/NotFound"; import { useCompany } from "./context/CompanyContext"; import { useDialogActions, useDialogState } from "./context/DialogContext"; import { loadLastInboxTab } from "./lib/inbox"; import { isOnboardingWizardActive, onboardingStepForCompany, shouldRedirectCompanylessRouteToOnboarding, } from "./lib/onboarding-route"; import { filterHiddenInstanceSettingsPath, normalizeRememberedInstanceSettingsPath } from "./lib/instance-settings"; import { useCloudInstance } from "./hooks/useCloudInstance"; import { useStreamlinedUiEnabled } from "./hooks/useStreamlinedUiEnabled"; import { cloudStackCreateUrl } from "./lib/cloudLinks"; import { navigateTopLevel } from "@/lib/browserNavigation"; const CompanyExport = lazy(() => import("./pages/CompanyExport").then((module) => ({ default: module.CompanyExport })), ); const ProductionAgents = lazy(() => import("./pages/Agents.production").then((module) => ({ default: module.Agents })), ); const ProductionAgentDetail = lazy(() => import("./pages/AgentDetail.production").then((module) => ({ default: module.AgentDetail })), ); const ProductionRoutines = lazy(() => import("./pages/Routines.production").then((module) => ({ default: module.Routines })), ); const ProductionRoutineDetail = lazy(() => import("./pages/RoutineDetail.production").then((module) => ({ default: module.RoutineDetail })), ); const ProductionCompanySkills = lazy(() => import("./pages/CompanySkills.production").then((module) => ({ default: module.CompanySkills })), ); const ProductionCompanyActivity = lazy(() => import("./pages/audit/CompanyActivity.production").then((module) => ({ default: module.CompanyActivity })), ); const ProductionCosts = lazy(() => import("./pages/Costs.production").then((module) => ({ default: module.Costs })), ); const ProductionOrgChart = lazy(() => import("./pages/OrgChart.production").then((module) => ({ default: module.OrgChart })), ); function ProductionSurface({ children }: { children: ReactNode }) { return }>{children}; } function boardRoutes(streamlinedUiEnabled: boolean) { return ( <> } /> } /> } /> : } /> } /> } /> } /> } /> } /> }> } /> } /> {/* Invites moved into the Members page; the old URL redirects (and stays gated so a hidden Invites surface never round-trips through it). */} }> } /> }> }> )} /> }> }> } /> }> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> {/* Connector health is inline on the Apps landing page; keep legacy links working. */} } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> }> } /> } /> } /> }> } /> } /> } /> }> } /> }> } /> }> } /> } /> }> } /> } /> } /> } /> } /> } /> : } /> } /> } /> } /> : } /> } /> {AGENT_FILTER_TABS.map((tab) => ( : } /> ))} } /> : } /> : } /> : } /> } /> } /> } /> } /> } /> }> } /> } /> } /> } /> }> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> {import.meta.env.DEV ? ( } /> ) : null} : } /> } /> } /> } /> } /> {/* Back-compat: the board lived at /status-cards before PAP-15223. */} } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> : } /> : } /> }> }> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> : } /> {streamlinedUiEnabled ? ( <> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> ) : ( <> } /> } /> )} {/* Conference Room Chat surfaces (PAP-136/PAP-137): routes stay registered but redirect to the company home while the experimental flag is off. The board-level `artifacts` mount below is the new conference-room one; the master-level mount above it still serves `/artifacts` in both modes. */} }> } /> } /> {/* Task chat dev harness — dev builds only. */} {import.meta.env.DEV ? ( } /> ) : null} } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> ); } function AppsConnectEntryRoute({ credentialSource = "paperclip_vault", }: { credentialSource?: ToolConnectionCredentialSource; } = {}) { const location = useLocation(); const searchParams = new URLSearchParams(location.search); return canEnterAppsConnect(searchParams) ? : ; } function InboxRootRedirect() { return ; } function LegacySkillStudioRedirect() { const location = useLocation(); const { companies, selectedCompany, loading } = useCompany(); const { companyPrefix, skillId } = useParams<{ companyPrefix?: string; skillId?: string }>(); if (loading) return null; const targetCompany = (companyPrefix ? companies.find((company) => company.issuePrefix.toUpperCase() === companyPrefix.toUpperCase()) : null) ?? selectedCompany ?? companies[0] ?? null; if (!targetCompany || !skillId) { return ; } return ( ); } function LegacySettingsRedirect() { const location = useLocation(); const { companies, selectedCompany, loading } = useCompany(); const { companyPrefix } = useParams<{ companyPrefix?: string }>(); const { hidden: hiddenSettings } = useHiddenSettings(); if (loading) { return ; } const targetCompany = (companyPrefix ? companies.find((company) => company.issuePrefix.toUpperCase() === companyPrefix.toUpperCase()) : null) ?? selectedCompany ?? companies[0] ?? null; if (!targetCompany) { if ( shouldRedirectCompanylessRouteToOnboarding({ pathname: location.pathname, hasCompanies: false, }) ) { return ; } return ; } const normalizedPath = filterHiddenInstanceSettingsPath( normalizeRememberedInstanceSettingsPath( `${location.pathname}${location.search}${location.hash}`, ), hiddenSettings, ); return ( ); } function LegacyToolsSettingsRedirect() { const { tab } = useParams<{ tab?: string }>(); return ; } // The developer "Tools" surface moved under the Apps "Advanced setup" door // (PAP-10862). `/tools` and `/tools/:tab` redirect to their new home. function LegacyToolsRedirect() { const { tab } = useParams<{ tab?: string }>(); return ; } function legacyToolsRedirectTarget(tab?: string) { if (!tab) return "/apps/advanced/profiles"; if (tab === "applications" || tab === "connections" || tab === "overview" || tab === "examples") return "/apps"; if (tab === "runtime" || tab === "audit") return "/apps"; if (tab === "policies") return "/apps/advanced/profiles"; return `/apps/advanced/${tab}`; } export function OnboardingRoutePage() { const { companies } = useCompany(); const { openOnboarding } = useDialogActions(); const { t } = useTranslation(); const cloudInstance = useCloudInstance(); const createStackUrl = cloudStackCreateUrl(cloudInstance?.cloudBaseUrl ?? null); const { onboardingOpen, onboardingRouteDismissed } = useDialogState(); const { companyPrefix } = useParams<{ companyPrefix?: string }>(); const matchedCompany = companyPrefix ? companies.find((company) => company.issuePrefix.toUpperCase() === companyPrefix.toUpperCase()) ?? null : null; // The OnboardingWizard auto-opens on this route (and can also be opened // explicitly). While it is showing it covers the whole screen, so the // launcher card below must not stay interactive behind it — otherwise users // can tab/click through to the form behind the modal (PAP-52). The launcher // only needs to render as a re-entry point once the wizard is dismissed. if (isOnboardingWizardActive({ onboardingOpen, routeDismissed: onboardingRouteDismissed })) { return null; } const title = matchedCompany ? `Add another agent to ${matchedCompany.name}` : companies.length > 0 ? "Create another organization" : "Create your first organization"; const description = matchedCompany ? "Run onboarding again to add an agent and a starter task for this organization." : companies.length > 0 ? "Run onboarding again to create another organization and seed its first agent." : "Get started by creating an organization and your first agent."; return (

{title}

{description}

{/* On a managed stack whose Cloud origin is unknown there is nowhere to send this click: creation lives on Cloud, and in-app creation is a 403 floor. A button that does nothing is worse than none, so say why instead of rendering an inert control. */} {!matchedCompany && cloudInstance && !createStackUrl ? (

{t("app.cloudCreateUnavailable", { defaultValue: "Organizations are created in Paperclip Cloud. This instance can't reach it right now — try again from your Cloud portfolio.", })}

) : ( )}
); } function CompanyRootRedirect() { const { companies, selectedCompany, loading } = useCompany(); const location = useLocation(); if (loading) { return ; } const targetCompany = selectedCompany ?? companies[0] ?? null; if (!targetCompany) { if ( shouldRedirectCompanylessRouteToOnboarding({ pathname: location.pathname, hasCompanies: false, }) ) { return ; } return ; } return ; } function StatusCardsLegacyRedirect() { const { cardId } = useParams<{ cardId?: string }>(); const prefix = useActiveCompanyPrefix(); const base = prefix ? `/${prefix}` : ""; return ; } function AuditCompatibilityRedirect({ to, forceAgentMode = false, }: { to: string; forceAgentMode?: boolean; }) { const location = useLocation(); const searchParams = new URLSearchParams(location.search); if (forceAgentMode) searchParams.set("mode", "agents"); const search = searchParams.toString(); return ; } function UnprefixedBoardRedirect() { const location = useLocation(); const { companies, selectedCompany, loading } = useCompany(); if (loading) { return ; } const targetCompany = selectedCompany ?? companies[0] ?? null; if (!targetCompany) { if ( shouldRedirectCompanylessRouteToOnboarding({ pathname: location.pathname, hasCompanies: false, }) ) { return ; } return ; } return ( ); } function NoCompaniesStartPage() { const { openOnboarding } = useDialogActions(); const { t } = useTranslation(); // A managed stack with no visible companies is a loading or error state, not // an invitation to create one in-app — creation lives on Cloud (403 floor). const cloudInstance = useCloudInstance(); const createStackUrl = cloudStackCreateUrl(cloudInstance?.cloudBaseUrl ?? null); return (

{t("app.noCompanies.title", { defaultValue: "Create your first organization" })}

{t("app.noCompanies.description", { defaultValue: "Get started by creating an organization." })}

{/* Same as the onboarding route: no Cloud origin means nowhere to send the click, and in-app creation is a 403 floor here. */} {cloudInstance && !createStackUrl ? (

{t("app.cloudCreateUnavailable", { defaultValue: "Organizations are created in Paperclip Cloud. This instance can't reach it right now — try again from your Cloud portfolio.", })}

) : ( )}
); } export function App() { const { enabled: streamlinedUiEnabled, loaded: streamlinedUiLoaded } = useStreamlinedUiEnabled(); return ( <> } /> } /> } /> } /> } /> } /> } /> } /> : }> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> {streamlinedUiEnabled ? ( <> } /> } /> } /> } /> } /> } /> ) : null} } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> {AGENT_FILTER_TABS.map((tab) => ( } /> ))} } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> : }> {boardRoutes(streamlinedUiEnabled)} } /> ); }