From beb70cff79d9b6bac79f6f85f22ff837e363272a Mon Sep 17 00:00:00 2001 From: mohiit1502 Date: Sun, 3 May 2026 18:10:34 +0530 Subject: [PATCH] chore: remove pre-branch-compare backup files --- webapp/src/App.tsx.bak.pre-branch-compare | 183 ------- .../layout/Navbar.tsx.bak.pre-branch-compare | 484 ------------------ .../ShellLayout.tsx.bak.pre-branch-compare | 388 -------------- .../apps/AppsPage.tsx.bak.pre-branch-compare | 289 ----------- .../apps/index.ts.bak.pre-branch-compare | 1 - 5 files changed, 1345 deletions(-) delete mode 100644 webapp/src/App.tsx.bak.pre-branch-compare delete mode 100644 webapp/src/components/layout/Navbar.tsx.bak.pre-branch-compare delete mode 100644 webapp/src/components/layout/ShellLayout.tsx.bak.pre-branch-compare delete mode 100644 webapp/src/pages/apps/AppsPage.tsx.bak.pre-branch-compare delete mode 100644 webapp/src/pages/apps/index.ts.bak.pre-branch-compare diff --git a/webapp/src/App.tsx.bak.pre-branch-compare b/webapp/src/App.tsx.bak.pre-branch-compare deleted file mode 100644 index 7e037c7d..00000000 --- a/webapp/src/App.tsx.bak.pre-branch-compare +++ /dev/null @@ -1,183 +0,0 @@ -import { lazy, Suspense } from 'react' -import { BrowserRouter, Routes, Route, Navigate, useSearchParams } from 'react-router-dom' -import { Layout } from '@/components/layout/Layout' -import { ProtectedRoute } from '@/auth/ProtectedRoute' -import { useAuth } from '@/auth/useAuth' -import { ToastProvider } from '@/components/Toast' -import { ModeGuard } from '@/components/ModeGuard' -import { GoogleOneTapPopup } from '@/components/GoogleOneTapPopup' -import { InstallableRoute } from '@/components/InstallableRoute' - -// ── Eagerly loaded (needed before auth) ────────────────────────────────────── -import { Landing } from '@/pages/Landing' -import { Login } from '@/pages/Login' -import { AuthCallback } from '@/pages/AuthCallback' -import { Stub } from '@/pages/Stub' -import { Terms } from '@/pages/Terms' -import { Privacy } from '@/pages/Privacy' - -function AgentsNewRedirect() { - const [params] = useSearchParams() - const skill = params.get('skill') ?? params.get('name') ?? '' - const dest = `/agents?spawn=1${skill ? `&skill=${encodeURIComponent(skill)}` : ''}` - return -} - -function HomeRoute() { - const { isLoading, isAuthenticated } = useAuth() - - if (isLoading) { - return - } - - if (isAuthenticated) { - return - } - - return -} - -function EmptyRoute() { - return null -} - -// ── Route-level lazy chunks — each page loads only when navigated to ───────── -const ControlPlane = lazy(() => import('@/pages/control-plane').then(m => ({ default: m.ControlPlane }))) -const AgentsPage = lazy(() => import('@/pages/agents').then(m => ({ default: m.AgentsPage }))) -const AgentDetailPage = lazy(() => import('@/pages/agents').then(m => ({ default: m.AgentDetailPage }))) -const AppsPage = lazy(() => import('@/pages/apps').then(m => ({ default: m.AppsPage }))) -const Tasks = lazy(() => import('@/pages/tasks').then(m => ({ default: m.Tasks }))) -const TaskDebuggerPage = lazy(() => import('@/pages/task-debugger').then(m => ({ default: m.TaskDebuggerPage }))) -const Plugins = lazy(() => import('@/pages/plugins').then(m => ({ default: m.Plugins }))) -const PluginDetail = lazy(() => import('@/pages/plugins').then(m => ({ default: m.PluginDetail }))) -const Policies = lazy(() => import('@/pages/policies').then(m => ({ default: m.Policies }))) -const PolicyDetail = lazy(() => import('@/pages/policies').then(m => ({ default: m.PolicyDetail }))) -const Security = lazy(() => import('@/pages/security').then(m => ({ default: m.Security }))) -const SecurityCenter = lazy(() => import('@/pages/security-center').then(m => ({ default: m.SecurityCenter }))) -const VulnerabilityDetail = lazy(() => import('@/pages/security-center').then(m => ({ default: m.VulnerabilityDetail }))) -const Logs = lazy(() => import('@/pages/logs').then(m => ({ default: m.Logs }))) -const Settings = lazy(() => import('@/pages/settings').then(m => ({ default: m.Settings }))) -const Integrations = lazy(() => import('@/pages/integrations').then(m => ({ default: m.Integrations }))) -const IntegrationCatalogDetail = lazy(() => import('@/pages/integrations').then(m => ({ default: m.IntegrationCatalogDetail }))) -const IntegrationInstanceDetail = lazy(() => import('@/pages/integrations').then(m => ({ default: m.IntegrationInstanceDetail }))) -const SchedulesPage = lazy(() => import('@/pages/schedules').then(m => ({ default: m.SchedulesPage }))) -const ScheduleDetailPage = lazy(() => import('@/pages/schedules').then(m => ({ default: m.ScheduleDetailPage }))) -const Models = lazy(() => import('@/pages/models').then(m => ({ default: m.Models }))) -const ContextBank = lazy(() => import('@/pages/context-bank').then(m => ({ default: m.ContextBank }))) -const Approvals = lazy(() => import('@/pages/approvals').then(m => ({ default: m.Approvals }))) -const KnowledgeBase = lazy(() => import('@/pages/knowledge-base').then(m => ({ default: m.KnowledgeBase }))) -const Workflows = lazy(() => import('@/pages/workflows').then(m => ({ default: m.Workflows }))) -const WorkflowDetail = lazy(() => import('@/pages/workflows').then(m => ({ default: m.WorkflowDetail }))) -const Marketplace = lazy(() => import('@/pages/marketplace').then(m => ({ default: m.Marketplace }))) -const MarketplaceItemDetail = lazy(() => import('@/pages/marketplace').then(m => ({ default: m.MarketplaceItemDetail }))) -const Docs = lazy(() => import('@/pages/Docs').then(m => ({ default: m.Docs }))) -const BlogList = lazy(() => import('@/pages/blog').then(m => ({ default: m.BlogList }))) -const BlogPost = lazy(() => import('@/pages/blog').then(m => ({ default: m.BlogPost }))) -const BlogNew = lazy(() => import('@/pages/blog').then(m => ({ default: m.BlogNew }))) -const BlogEdit = lazy(() => import('@/pages/blog').then(m => ({ default: m.BlogEdit }))) -const QnAList = lazy(() => import('@/pages/qna').then(m => ({ default: m.QnAList }))) -const QnAPost = lazy(() => import('@/pages/qna').then(m => ({ default: m.QnAPost }))) -const QnANew = lazy(() => import('@/pages/qna').then(m => ({ default: m.QnANew }))) -const QnAEdit = lazy(() => import('@/pages/qna').then(m => ({ default: m.QnAEdit }))) -const BuildDashboard = lazy(() => import('@/pages/build-dashboard').then(m => ({ default: m.BuildDashboard }))) -const Analytics = lazy(() => import('@/pages/analytics').then(m => ({ default: m.Analytics }))) -const Profile = lazy(() => import('@/pages/Profile')) -const UpgradeWall = lazy(() => import('@/pages/quota/UpgradeWall').then(m => ({ default: m.UpgradeWall }))) -const GetMoreRuns = lazy(() => import('@/pages/quota/GetMoreRuns').then(m => ({ default: m.GetMoreRuns }))) -const AdminQuota = lazy(() => import('@/pages/quota/AdminQuota').then(m => ({ default: m.AdminQuota }))) - -// ── Minimal spinner shown during chunk fetch ────────────────────────────────── -function PageFallback() { - return ( -
- -
- ) -} - -function App() { - return ( - - - - }> - - {/* Auth routes — no layout, no protection */} - } /> - } /> - - } /> - - {/* Public content routes — readable without login */} - }> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - - - {/* All app routes share the Navbar layout + require auth */} - }> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - - - - - - ) -} - -export default App diff --git a/webapp/src/components/layout/Navbar.tsx.bak.pre-branch-compare b/webapp/src/components/layout/Navbar.tsx.bak.pre-branch-compare deleted file mode 100644 index 225f295d..00000000 --- a/webapp/src/components/layout/Navbar.tsx.bak.pre-branch-compare +++ /dev/null @@ -1,484 +0,0 @@ -import { useState, useRef, useEffect } from 'react' -import { Link, useLocation } from 'react-router-dom' -import { cn } from '@/lib/utils' -import { useTheme } from '@/theme' -import { useProfile } from '@/hooks/useProfile' -import { NotificationsPanel } from './NotificationsPanel' -import { useShell } from '@/context/ShellContext' -import { - Bot, - ListTodo, - Settings, - Sun, - Moon, - BookOpen, - Library, - MessagesSquare, - Menu, - X, - GitBranch, - LayoutGrid, - Bell, - User, - LogOut, - Search, - TerminalSquare, - MoreHorizontal, -} from 'lucide-react' -import { ChatAiIcon } from '@/components/icons/ChatAiIcon' -import { useAuth } from '@/auth/useAuth' -import { iam } from '@/auth/iam' -import { useQuery } from '@tanstack/react-query' -import { installablesApi, navApi, notificationsApi } from '@/api/client' -import { useNotificationsWS } from '@/hooks/useNotificationsWS' -import { QuotaStatusWidget } from '@/components/QuotaStatusWidget' -import { useNetworkStatus } from '@/hooks/useNetworkStatus' -import { SystemStatusDot, SystemStatusModal, NetworkAlertBanner } from '@/components/SystemStatusDot' -import { DevSimulator } from '@/components/DevSimulator' - -const PRIMARY_NAV = [ - { path: '/agents', label: 'Agents', icon: Bot, countKey: 'agents' as const }, - { path: '/tasks', label: 'Tasks', icon: ListTodo, countKey: 'tasks' as const }, - { path: '/workflows', label: 'Workflows', icon: GitBranch, countKey: 'workflows' as const }, -] - -type EntityCounts = { agents: number; tasks: number; workflows: number; plugins: number; policies: number } -type LiveStats = { agents: number; tasks: number; workflows: number; plugins: number } - -function useNavCounts(enabled: boolean) { - const { data } = useQuery({ - queryKey: ['nav-counts'], - queryFn: () => navApi.counts(), - staleTime: 60_000, - enabled, - }) - return data -} - - -type Popover = 'notif' | 'profile' | 'more' | null - -export function Navbar() { - const { pathname } = useLocation() - const { theme, toggleWithTransition } = useTheme() - const { terminalVisible, terminalCollapsed, setTerminalVisible, setTerminalCollapsed } = useShell() - const { profile } = useProfile() - const { isAuthenticated, logout } = useAuth() - const [activePopover, setActivePopover] = useState(null) - const notifRef = useRef(null) - const profileRef = useRef(null) - const moreRef = useRef(null) - const [mobileOpen, setMobileOpen] = useState(false) - const networkStatus = useNetworkStatus() - const [statusModalOpen, setStatusModalOpen] = useState(false) - const [isMobile, setIsMobile] = useState(() => window.matchMedia('(max-width: 640px)').matches) - // Global real-time channel: notifications + approvals. - useNotificationsWS(isAuthenticated) - const navCounts = useNavCounts(isAuthenticated) - const { data: installables = [] } = useQuery({ - queryKey: ['installables'], - queryFn: () => installablesApi.list(), - enabled: isAuthenticated, - staleTime: 60_000, - }) - const counts: EntityCounts = { - agents: navCounts?.agents ?? 0, - tasks: navCounts?.tasks ?? 0, - workflows: navCounts?.workflows ?? 0, - plugins: navCounts?.plugins ?? 0, - policies: navCounts?.policies ?? 0, - } - const liveStats: LiveStats = { - agents: navCounts?.agents_running ?? 0, - tasks: navCounts?.tasks_running ?? 0, - workflows: navCounts?.workflows_active ?? 0, - plugins: navCounts?.plugins ?? 0, - } - const { data: unreadData } = useQuery({ - queryKey: ['notifications-unread'], - queryFn: () => notificationsApi.unreadCount(), - enabled: isAuthenticated, - }) - const unreadCount = unreadData?.unread ?? 0 - const installedNavSlugs = new Set(installables.filter(item => item.installed).map(item => item.slug)) - const terminalInstalled = installedNavSlugs.has('terminal') - const desktopNav = PRIMARY_NAV.filter(item => installedNavSlugs.has(item.path.slice(1))) - const overflowInstallables = installables.filter(item => item.installed && !!item.route && !['agents', 'tasks', 'workflows', 'terminal', 'constellation', 'terms'].includes(item.slug)) - const mobileNav = desktopNav - - const togglePopover = (p: Popover) => setActivePopover(prev => prev === p ? null : p) - - useEffect(() => { - if (!activePopover) return - function handleClick(e: MouseEvent) { - const target = e.target as Node - const refs: Record> = { - notif: notifRef, - profile: profileRef, - more: moreRef, - } - const ref = refs[activePopover!] - if (ref?.current && !ref.current.contains(target)) { - setActivePopover(null) - } - } - document.addEventListener('mousedown', handleClick) - return () => document.removeEventListener('mousedown', handleClick) - }, [activePopover]) - - useEffect(() => { - setMobileOpen(false) - setActivePopover(null) - }, [pathname]) - - useEffect(() => { - if (mobileOpen) { - document.body.style.overflow = 'hidden' - } else { - document.body.style.overflow = '' - } - return () => { document.body.style.overflow = '' } - }, [mobileOpen]) - - useEffect(() => { - const mq = window.matchMedia('(max-width: 640px)') - const handler = (e: MediaQueryListEvent) => setIsMobile(e.matches) - mq.addEventListener('change', handler) - return () => mq.removeEventListener('change', handler) - }, []) - - return ( - <> - - - {/* Network alert banner — visible when degraded/offline */} - setStatusModalOpen(true)} /> - - {/* System status modal / bottom drawer */} - {statusModalOpen && ( - setStatusModalOpen(false)} - isMobile={isMobile} - /> - )} - { - mobileOpen && ( -
setMobileOpen(false)} /> - ) - } -
-
- {isAuthenticated && ( - <> -
Navigation
- {mobileNav.map(({ path, label, icon: Icon }) => { - const active = pathname === path || (path !== '/' && path !== '/blog' && pathname.startsWith(path)) - return ( - setMobileOpen(false)} - > - - {label} - {active && } - - ) - })} -
- - )} -
More
- {isAuthenticated && ( - setMobileOpen(false)} - > - - Settings - {pathname.startsWith('/settings') && } - - )} - setMobileOpen(false)} - > - - Blog - {pathname.startsWith('/blog') && } - - setMobileOpen(false)} - > - - Q&A - {pathname.startsWith('/qna') && } - - setMobileOpen(false)}> - - Docs - - {isAuthenticated && terminalInstalled && ( - - )} -
- -
-
- - - ) -} diff --git a/webapp/src/components/layout/ShellLayout.tsx.bak.pre-branch-compare b/webapp/src/components/layout/ShellLayout.tsx.bak.pre-branch-compare deleted file mode 100644 index 53932465..00000000 --- a/webapp/src/components/layout/ShellLayout.tsx.bak.pre-branch-compare +++ /dev/null @@ -1,388 +0,0 @@ -import { useEffect, useRef, useCallback, useState } from 'react' -import { useQuery } from '@tanstack/react-query' -import { Outlet, useLocation } from 'react-router-dom' -import { GlobalContextPanel } from './SessionsPanel' -import { ChatWorkspace } from './ChatWorkspace' -import { RightPanel } from './RightPanel' -import { useProMode } from '@/hooks/useProMode' -import { useViewMode } from '@/context/ViewModeContext' -import { useShell } from '@/context/ShellContext' -import { AssistantDashboard } from '@/pages/AssistantDashboard' -import { LayoutDashboard } from 'lucide-react' -import { approvalsApi, chatSessionsApi, tasksApi } from '@/api/client' - -// ── Mobile detection hook ───────────────────────────────────────────────────── -function useIsMobile() { - const [mobile, setMobile] = useState(() => window.innerWidth <= 640) - const [tablet, setTablet] = useState(() => window.innerWidth > 640 && window.innerWidth <= 768) - useEffect(() => { - const mq = window.matchMedia('(max-width: 40rem)') - const mqTablet = window.matchMedia('(min-width: 40.0625rem) and (max-width: 48rem)') - const onMQ = (e: MediaQueryListEvent) => setMobile(e.matches) - const onMQTablet = (e: MediaQueryListEvent) => setTablet(e.matches) - mq.addEventListener('change', onMQ) - mqTablet.addEventListener('change', onMQTablet) - return () => { mq.removeEventListener('change', onMQ); mqTablet.removeEventListener('change', onMQTablet) } - }, []) - return { isMobile: mobile, isTablet: tablet } -} - -type MobilePane = 'global' | 'chat' | 'inspector' - -// Routes always rendered full-page (no 3-pane, no mode guard) -const FULLPAGE_ROUTES = [ - '/settings', '/profile', '/docs', '/blog', '/blogs', '/qna', -] - -// Routes shown in 3-pane center when pro mode is on -const PRO_PAGE_ROUTES = [ - '/control', '/apps', '/surfaces', '/agents', '/tasks', '/plugins', '/policies', '/security', - '/security-center', '/logs', '/integrations', '/models', '/context-bank', - '/marketplace', '/approvals', '/knowledge', '/workflows', - '/build', '/analytics', '/admin', '/quota', '/memory', -] - -function isFullPageRoute(pathname: string): boolean { - return FULLPAGE_ROUTES.some(r => pathname === r || pathname.startsWith(r + '/')) -} - -function isProPageRoute(pathname: string): boolean { - return PRO_PAGE_ROUTES.some(r => pathname === r || pathname.startsWith(r + '/')) -} - -// ── Storage keys ────────────────────────────────────────────────────────────── -const LEFT_KEY = 'nebula_shell_left_ratio' -const RIGHT_KEY = 'nebula_shell_right_ratio' - -// ── Defaults and constraints ────────────────────────────────────────────────── -const DEFAULT_LEFT = 0.20 // 20% sessions panel -const DEFAULT_RIGHT = 0.27 // 27% right panel -const MIN_LEFT = 0.12 -const MAX_LEFT = 0.35 -const MIN_RIGHT = 0.18 -const MAX_RIGHT = 0.45 - -function clamp(v: number, lo: number, hi: number) { - return Math.max(lo, Math.min(hi, v)) -} - -function readRatio(key: string, fallback: number): number { - try { - const v = localStorage.getItem(key) - if (v === null) return fallback - const n = parseFloat(v) - return isNaN(n) ? fallback : n - } catch { - return fallback - } -} - -// ── Resize handle ───────────────────────────────────────────────────────────── - -interface HandleProps { - onMouseDown: (e: React.MouseEvent) => void -} - -function ResizeHandle({ onMouseDown }: HandleProps) { - return ( -
(e.currentTarget as HTMLElement).style.background = 'var(--nbl-cyan)'} - onMouseLeave={e => (e.currentTarget as HTMLElement).style.background = 'var(--nbl-border)'} - /> - ) -} - -// ── Shell layout ────────────────────────────────────────────────────────────── - -export function ShellLayout() { - const { pathname } = useLocation() - const { proMode } = useProMode() - const { isAssist } = useViewMode() - const { chatExpanded, setChatExpanded, selectedItem, focusedChatMessage, activeSessionId, allChatMessages } = useShell() - const { isMobile, isTablet } = useIsMobile() - const [mobilePane, setMobilePane] = useState('chat') - - // Full-page routes (settings/profile/blog/docs): render as Outlet without 3-pane shell - const fullPage = isFullPageRoute(pathname) - // Pro-mode page routes: render Outlet in center panel when pro mode is on - const showPageInCenter = proMode && isProPageRoute(pathname) - // Assistant mode: show dashboard on home route unless chat is expanded - const showDashboard = isAssist && !chatExpanded && pathname === '/chat' - - const [leftRatio, setLeftRatio] = useState(() => clamp(readRatio(LEFT_KEY, DEFAULT_LEFT), MIN_LEFT, MAX_LEFT)) - const [rightRatio, setRightRatio] = useState(() => clamp(readRatio(RIGHT_KEY, DEFAULT_RIGHT), MIN_RIGHT, MAX_RIGHT)) - const touchStartX = useRef(0) - const containerRef = useRef(null) - const draggingRef = useRef<'left' | 'right' | null>(null) - const rafRef = useRef(0) - - // Persist to localStorage on change - useEffect(() => { localStorage.setItem(LEFT_KEY, String(leftRatio)) }, [leftRatio]) - useEffect(() => { localStorage.setItem(RIGHT_KEY, String(rightRatio)) }, [rightRatio]) - - const startDrag = useCallback((which: 'left' | 'right') => (e: React.MouseEvent) => { - e.preventDefault() - draggingRef.current = which - document.body.style.cursor = 'col-resize' - document.body.style.userSelect = 'none' - - const onMove = (ev: MouseEvent) => { - if (!draggingRef.current || !containerRef.current) return - cancelAnimationFrame(rafRef.current) - rafRef.current = requestAnimationFrame(() => { - const rect = containerRef.current!.getBoundingClientRect() - const frac = (ev.clientX - rect.left) / rect.width - - if (draggingRef.current === 'left') { - setLeftRatio(clamp(frac, MIN_LEFT, MAX_LEFT)) - } else { - // right handle: frac is distance from left edge, so right ratio = 1 - frac - const rr = clamp(1 - frac, MIN_RIGHT, MAX_RIGHT) - setRightRatio(rr) - } - }) - } - - const onUp = () => { - draggingRef.current = null - document.body.style.cursor = '' - document.body.style.userSelect = '' - document.removeEventListener('mousemove', onMove) - document.removeEventListener('mouseup', onUp) - } - - document.addEventListener('mousemove', onMove) - document.addEventListener('mouseup', onUp) - }, []) - - // Full-page routes bypass 3-pane shell entirely - if (fullPage) { - return ( -
- -
- ) - } - - // Left panel only makes sense on the chat route — sessions are chat-specific - const isChatRoute = pathname === '/chat' - - const { data: sessionDetail } = useQuery({ - queryKey: ['chat-session-detail', activeSessionId], - queryFn: () => chatSessionsApi.get(activeSessionId!), - enabled: isChatRoute && !!activeSessionId, - staleTime: 15_000, - refetchInterval: 20_000, - }) - - const sessionTaskIds = Array.from(new Set((sessionDetail?.entities ?? []) - .filter(item => item.entity_type === 'task') - .map(item => item.entity_id))) - - const sessionApprovalIds = Array.from(new Set(allChatMessages - .map(msg => msg.approvalId) - .filter((id): id is string => !!id))) - - const { data: sessionTasks = [] } = useQuery({ - queryKey: ['chat-session-tasks', activeSessionId, sessionTaskIds], - queryFn: () => Promise.all(sessionTaskIds.map(id => tasksApi.get(id))), - enabled: isChatRoute && !!activeSessionId && sessionTaskIds.length > 0, - staleTime: 5_000, - refetchInterval: 8_000, - }) - - const { data: sessionApprovals = [] } = useQuery({ - queryKey: ['chat-session-approvals', activeSessionId, sessionApprovalIds], - queryFn: () => Promise.all(sessionApprovalIds.map(id => approvalsApi.get(id))), - enabled: isChatRoute && !!activeSessionId && sessionApprovalIds.length > 0, - staleTime: 5_000, - refetchInterval: 10_000, - }) - - const runtimeCount = (sessionApprovals.filter(approval => approval.status === 'pending').length) - + (allChatMessages.filter(message => message.role === 'agent').length) - + (sessionTasks.filter(task => task.status === 'running').length) - + ([...sessionTasks].filter(task => task.status !== 'running').slice(0, 8).length) - + ((sessionDetail?.entities ?? []).length) - - // ── Mobile: swipe-based single-pane with top-corner indicators ──────────── - if (isMobile && isChatRoute) { - const PANES: MobilePane[] = ['global', 'chat', 'inspector'] - const paneIndex = PANES.indexOf(mobilePane) - - const centerContent = showDashboard ? ( - - ) : showPageInCenter ? ( -
- ) : ( - - ) - - // Touch swipe handler - const handleTouchStart = (e: React.TouchEvent) => { - touchStartX.current = e.touches[0].clientX - } - const handleTouchEnd = (e: React.TouchEvent) => { - const dx = e.changedTouches[0].clientX - touchStartX.current - if (Math.abs(dx) < 50) return - if (dx < 0 && paneIndex < PANES.length - 1) setMobilePane(PANES[paneIndex + 1]) - if (dx > 0 && paneIndex > 0) setMobilePane(PANES[paneIndex - 1]) - } - - return ( -
- {/* ── Active pane ──────────────────────────────────────────────── */} -
- {mobilePane === 'global' && } - {mobilePane === 'chat' && centerContent} - {mobilePane === 'inspector' && } -
- - {/* ── Left edge strip: tap or swipe right to go to previous pane ── */} - {paneIndex > 0 && ( -
- ) - } - - // ── Tablet: hide left panel, center + right only ──────────────────────── - const hideLeft = isTablet || !isChatRoute - const inspectorOpen = isChatRoute && (!!selectedItem || !!focusedChatMessage) - const rightPanelOpen = isChatRoute && (inspectorOpen || runtimeCount > 0) - - const leftPct = `${(leftRatio * 100).toFixed(2)}%` - const rightPct = rightPanelOpen - ? isTablet ? '40%' : `${(rightRatio * 100).toFixed(2)}%` - : '0%' - const centerPct = rightPanelOpen - ? hideLeft - ? isTablet ? '60%' : `${((1 - rightRatio) * 100).toFixed(2)}%` - : `${((1 - leftRatio - rightRatio) * 100).toFixed(2)}%` - : hideLeft - ? '100%' - : `${((1 - leftRatio) * 100).toFixed(2)}%` - - return ( -
- {/* ── Left: Global Context (chat route only, desktop) ──────────── */} - {isChatRoute && !isTablet && ( - <> -
- -
- - - )} - - {/* ── Center: Dashboard / Chat / Page ─────────────────────────── */} -
- {showDashboard ? ( - - ) : showPageInCenter ? ( -
- -
- ) : ( - <> - {isAssist && chatExpanded && pathname === '/chat' && ( -
- -
- )} - - - )} -
- - {/* ── Right: Chat-scoped runtime / inspector ────────────────────── */} - {rightPanelOpen && !isTablet && } - {rightPanelOpen && ( -
- -
- )} -
- ) -} diff --git a/webapp/src/pages/apps/AppsPage.tsx.bak.pre-branch-compare b/webapp/src/pages/apps/AppsPage.tsx.bak.pre-branch-compare deleted file mode 100644 index b4925e9f..00000000 --- a/webapp/src/pages/apps/AppsPage.tsx.bak.pre-branch-compare +++ /dev/null @@ -1,289 +0,0 @@ -import { useMemo, useState, type ElementType } from 'react' -import { Link } from 'react-router-dom' -import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query' -import { - Bot, - GitBranch, - LayoutGrid, - ListTodo, - Package, - Plug, - Shield, - BookOpen, - ChevronRight, - CheckCircle2, - Trash2, - Loader2, -} from 'lucide-react' -import { PageShell } from '@/components/nebula-ux' -import { agentsApi, corporaApi, installablesApi, integrationsApi, pluginsApi, policiesApi, workflowsApi, type ApiInstallable } from '@/api/client' - -const INSTALLABLE_ICONS: Record = { - bot: Bot, - 'git-branch': GitBranch, - 'list-todo': ListTodo, - package: Package, - plug: Plug, - shield: Shield, - 'shield-check': Shield, - 'book-open': BookOpen, - book: BookOpen, - sparkles: LayoutGrid, - 'layout-grid': LayoutGrid, -} - -type InstalledApp = { - id: string - name: string - route: string - icon: ElementType - accent: string - description: string - countLabel: string - countValue?: number - installable?: ApiInstallable -} - -export function AppsPage() { - const qc = useQueryClient() - const { data: agents } = useQuery({ - queryKey: ['apps-agents'], - queryFn: () => agentsApi.list({ page_size: 50 }), - }) - const { data: workflows } = useQuery({ - queryKey: ['apps-workflows'], - queryFn: () => workflowsApi.list({ page_size: 50 }), - }) - const { data: plugins } = useQuery({ - queryKey: ['apps-plugins'], - queryFn: () => pluginsApi.list({ page_size: 100 }), - }) - const { data: policies } = useQuery({ - queryKey: ['apps-policies'], - queryFn: () => policiesApi.list({ page_size: 50 }), - }) - const { data: knowledge } = useQuery({ - queryKey: ['apps-knowledge'], - queryFn: () => corporaApi.list({ page_size: 50 }), - }) - const { data: integrations } = useQuery({ - queryKey: ['apps-integrations'], - queryFn: () => integrationsApi.instances(), - }) - const { data: installables = [] } = useQuery({ - queryKey: ['installables'], - queryFn: () => installablesApi.list(), - }) - const [busyInstallable, setBusyInstallable] = useState(null) - - const installableMut = useMutation({ - mutationFn: (item: ApiInstallable) => item.installed ? installablesApi.uninstall(item.slug) : installablesApi.install(item.slug), - onMutate: (item) => setBusyInstallable(item.slug), - onSuccess: () => qc.invalidateQueries({ queryKey: ['installables'] }), - onSettled: () => setBusyInstallable(null), - }) - - const enabledPlugins = (plugins?.items ?? []).filter(plugin => plugin.enabled) - const installableMap = new Map(installables.map(item => [item.slug, item])) - - const installedApps = useMemo(() => { - const coreApps: InstalledApp[] = [ - { - id: 'agents', - name: 'Agents', - route: '/agents', - icon: Bot, - accent: 'var(--nbl-cyan)', - description: 'Create, inspect, and operate the agent workforce behind your runtime.', - countLabel: 'Installed', - countValue: agents?.total ?? agents?.items?.length ?? 0, - installable: installableMap.get('agents'), - }, - { - id: 'tasks', - name: 'Tasks', - route: '/tasks', - icon: ListTodo, - accent: 'var(--nbl-green)', - description: 'Track live executions, retries, failures, and final outcomes.', - countLabel: 'Installed', - installable: installableMap.get('tasks'), - }, - { - id: 'workflows', - name: 'Workflows', - route: '/workflows', - icon: GitBranch, - accent: 'var(--nbl-amber)', - description: 'Design and run repeatable automations with observable execution paths.', - countLabel: 'Installed', - countValue: workflows?.total ?? workflows?.items?.length ?? 0, - installable: installableMap.get('workflows'), - }, - ].filter(app => app.installable?.installed) - - if (installableMap.get('plugins')?.installed) { - coreApps.push({ - id: 'plugins', - name: 'Plugins', - route: '/plugins', - icon: Package, - accent: 'var(--nbl-purple)', - description: 'Installed tools and extensions that expand what your runtime can do.', - countLabel: 'Enabled', - countValue: enabledPlugins.length, - installable: installableMap.get('plugins'), - }) - } - - if (installableMap.get('policies')?.installed) { - coreApps.push({ - id: 'policies', - name: 'Policies', - route: '/policies', - icon: Shield, - accent: 'var(--nbl-red)', - description: 'Guardrails, approvals, and enforcement logic active in your system.', - countLabel: 'Active', - countValue: policies?.items?.length ?? 0, - installable: installableMap.get('policies'), - }) - } - - if (installableMap.get('knowledge')?.installed) { - coreApps.push({ - id: 'knowledge', - name: 'Knowledge', - route: '/knowledge', - icon: BookOpen, - accent: 'var(--nbl-green)', - description: 'Corpora and sources currently indexed for retrieval and memory augmentation.', - countLabel: 'Corpora', - countValue: knowledge?.corpora?.length ?? 0, - installable: installableMap.get('knowledge'), - }) - } - - if (installableMap.get('integrations')?.installed) { - coreApps.push({ - id: 'integrations', - name: 'Integrations', - route: '/integrations', - icon: Plug, - accent: 'var(--nbl-cyan)', - description: 'Connected services and external systems available to your runtime.', - countLabel: 'Connected', - countValue: integrations?.length ?? 0, - installable: installableMap.get('integrations'), - }) - } - - const knownIds = new Set(coreApps.map(app => app.id)) - const extraApps = installables - .filter(item => item.installed && !!item.route) - .filter(item => !['constellation', 'terms', 'terminal'].includes(item.slug)) - .filter(item => !knownIds.has(item.slug)) - .map(item => ({ - id: item.slug, - name: item.name, - route: item.route!, - icon: INSTALLABLE_ICONS[item.icon ?? 'layout-grid'] ?? LayoutGrid, - accent: 'var(--nbl-nebula)', - description: item.description, - countLabel: item.kind === 'workspace' ? 'Installed' : item.kind === 'runtime' ? 'Enabled' : 'Available', - installable: item, - })) - - return [...coreApps, ...extraApps] - }, [agents?.items?.length, agents?.total, enabledPlugins.length, installableMap, installables, integrations?.length, knowledge?.corpora?.length, policies?.items?.length, workflows?.items?.length, workflows?.total]) - - return ( - -
-
-
- -

Apps

-
-

Open installed NebulaOS apps and workspace surfaces from one place. Chat remains native and is always available from the dedicated chat button.

-
- - Open Marketplace - - -
- - {installedApps.length === 0 && ( -
-
- -
-
No installed apps yet
-
- Install agents, tasks, workflows, or other workspace apps from the marketplace and they will appear here automatically. Chat remains native and is always available from the header. -
-
- - Open Marketplace - - -
-
- )} - -
- {installedApps.map((app, index) => { - const Icon = app.icon - const installable = app.installable - const removable = !!app.installable && !app.installable.default_installed - const busy = busyInstallable === installable?.slug - return ( - -
-
- -
-
- - {app.countValue != null ? `${app.countLabel} · ${app.countValue}` : app.countLabel} -
-
-
-
{app.name}
-
{app.description}
-
-
- Open -
- {removable && installable && ( - - )} - -
-
- - ) - })} -
-
- ) -} diff --git a/webapp/src/pages/apps/index.ts.bak.pre-branch-compare b/webapp/src/pages/apps/index.ts.bak.pre-branch-compare deleted file mode 100644 index f54a53be..00000000 --- a/webapp/src/pages/apps/index.ts.bak.pre-branch-compare +++ /dev/null @@ -1 +0,0 @@ -export { AppsPage } from './AppsPage'