@burdenoff/microfe-vibecontrols 2026.623.2 → 2026.623.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/actions/ActionTypeBadge.js +1 -1
- package/dist/components/actions/ActionTypeBadge.js.map +1 -1
- package/dist/components/agent-manager/AgentManagerAdminBar.js +1 -1
- package/dist/components/agent-manager/AgentManagerAdminBar.js.map +1 -1
- package/dist/components/agent-manager/AgentManagerChatPane.js +1 -1
- package/dist/components/agent-manager/AgentManagerChatPane.js.map +1 -1
- package/dist/components/agent-manager/AgentManagerInputArea.js +1 -1
- package/dist/components/agent-manager/AgentManagerInputArea.js.map +1 -1
- package/dist/components/agent-manager/BridleConfigPanel.js +2 -2
- package/dist/components/agent-manager/BridleConfigPanel.js.map +1 -1
- package/dist/components/agent-manager/ProfilesPanel.js +1 -1
- package/dist/components/agent-manager/ProfilesPanel.js.map +1 -1
- package/dist/components/agent-manager/controls/AutonomySelector.js +1 -1
- package/dist/components/agent-manager/controls/AutonomySelector.js.map +1 -1
- package/dist/components/agent-manager/controls/SdkSelector.js +1 -1
- package/dist/components/agent-manager/controls/SdkSelector.js.map +1 -1
- package/dist/components/agent-manager/controls/VoiceToggle.js +1 -1
- package/dist/components/agent-manager/controls/VoiceToggle.js.map +1 -1
- package/dist/components/agent-manager/detail/SessionLogsPanel.js +2 -2
- package/dist/components/agent-manager/detail/SessionLogsPanel.js.map +1 -1
- package/dist/components/agents/AgentForm.js +2 -2
- package/dist/components/agents/AgentForm.js.map +1 -1
- package/dist/components/ai/AIContextsTab.js +1 -1
- package/dist/components/ai/AIContextsTab.js.map +1 -1
- package/dist/components/ai/AIOverviewTab.js +1 -1
- package/dist/components/ai/AIOverviewTab.js.map +1 -1
- package/dist/components/ai/AIPromptsTab.js +1 -1
- package/dist/components/ai/AIPromptsTab.js.map +1 -1
- package/dist/components/ai/AIQueueTab.js +1 -1
- package/dist/components/ai/AIQueueTab.js.map +1 -1
- package/dist/components/ai/AISessionsTab.js +1 -1
- package/dist/components/ai/AISessionsTab.js.map +1 -1
- package/dist/components/audit/AuditLogTable.js +1 -1
- package/dist/components/audit/AuditLogTable.js.map +1 -1
- package/dist/components/docs/DocsPageTree.js +1 -1
- package/dist/components/docs/DocsPageTree.js.map +1 -1
- package/dist/components/graph/ConnectionPermissionsDialog.js +2 -2
- package/dist/components/graph/ConnectionPermissionsDialog.js.map +1 -1
- package/dist/components/graph/MeshDeployWizard.js +3 -3
- package/dist/components/graph/MeshDeployWizard.js.map +1 -1
- package/dist/components/graph/MeshRequestPanel.js +2 -2
- package/dist/components/graph/MeshRequestPanel.js.map +1 -1
- package/dist/components/notes/NoteCard.js +1 -1
- package/dist/components/notes/NoteCard.js.map +1 -1
- package/dist/components/notes/NoteEditor.js +1 -1
- package/dist/components/notes/NoteEditor.js.map +1 -1
- package/dist/components/notes/NoteList.js +1 -1
- package/dist/components/notes/NoteList.js.map +1 -1
- package/dist/components/shared/AgentSetupBanner.js +5 -5
- package/dist/components/shared/AgentSetupBanner.js.map +1 -1
- package/dist/components/shared/InstallTabs.js +17 -56
- package/dist/components/shared/InstallTabs.js.map +1 -1
- package/dist/components/templates/TemplateCard.js +1 -1
- package/dist/components/templates/TemplateCard.js.map +1 -1
- package/dist/components/vibecalendar/types.js +1 -1
- package/dist/components/vibecalendar/types.js.map +1 -1
- package/dist/components/vibedeck/VibeDeckButtonForm.js +1 -1
- package/dist/components/vibedeck/VibeDeckButtonForm.js.map +1 -1
- package/dist/components/vibedeck/VibeDeckExecutionLog.js +2 -2
- package/dist/components/vibedeck/VibeDeckExecutionLog.js.map +1 -1
- package/dist/components/vibedeck/VibeDeckForm.js +1 -1
- package/dist/components/vibedeck/VibeDeckForm.js.map +1 -1
- package/dist/components/vibedeck/VibeDeckGrid.js +1 -1
- package/dist/components/vibedeck/VibeDeckGrid.js.map +1 -1
- package/dist/pages/AgentsPage.js +2 -2
- package/dist/pages/AgentsPage.js.map +1 -1
- package/dist/pages/CatalogDetailPage.js +1 -1
- package/dist/pages/CatalogDetailPage.js.map +1 -1
- package/dist/pages/ComponentDetailPage.js +1 -1
- package/dist/pages/ComponentDetailPage.js.map +1 -1
- package/dist/pages/GettingStartedPage.js +130 -114
- package/dist/pages/GettingStartedPage.js.map +1 -1
- package/dist/pages/OverviewPage.js +10 -10
- package/dist/pages/OverviewPage.js.map +1 -1
- package/dist/pages/PluginsCatalogPage.js +1 -1
- package/dist/pages/PluginsCatalogPage.js.map +1 -1
- package/dist/pages/SessionsPage.js +3 -3
- package/dist/pages/SessionsPage.js.map +1 -1
- package/dist/pages/SharedSessionPage.js +2 -2
- package/dist/pages/SharedSessionPage.js.map +1 -1
- package/dist/pages/TargetsPage.js +2 -2
- package/dist/pages/TargetsPage.js.map +1 -1
- package/dist/pages/TemplateDetailPage.js +2 -2
- package/dist/pages/TemplateDetailPage.js.map +1 -1
- package/dist/pages/VibesPage.js +2 -2
- package/dist/pages/VibesPage.js.map +1 -1
- package/dist/pages/WebhooksPage.js +2 -2
- package/dist/pages/WebhooksPage.js.map +1 -1
- package/dist/pages/ai/AISessionDetailPage.js +2 -2
- package/dist/pages/ai/AISessionDetailPage.js.map +1 -1
- package/dist/translations/en.js +27 -52
- package/dist/translations/en.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"OverviewPage.js","names":[],"sources":["../../src/pages/OverviewPage.tsx"],"sourcesContent":["import { useCallback, useMemo } from 'react';\nimport { usePermissions } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { VIBE_PERMISSIONS } from '../constants/permissions';\nimport {\n Bot,\n Zap,\n Terminal,\n FileText,\n Activity,\n Server,\n Play,\n GitBranch,\n RefreshCw,\n} from 'lucide-react';\nimport { MotionPage, MotionList, MotionItem } from '@burdenoff/fe-libs/shared/motion';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { AgentCard } from '@/components/agents';\nimport { SessionCard } from '@/components/sessions';\nimport { VibeCard } from '@/components/vibes';\nimport {\n PageHeader,\n StatCard,\n Card,\n AgentSetupBanner,\n ErrorState,\n LoadingState,\n} from '@/components/shared';\nimport { useVibeNavigate, useVibeControls } from '@/providers/VibeControlsProvider';\nimport { useCachedInitialData } from '@/hooks/useCachedInitialData';\nimport { useListTargetsQuery } from '@/generated/wspace-operations';\nimport type { Vibe } from '@/components/vibes/types';\nimport type { Session } from '@/components/sessions/types';\n\n/**\n * Overview page showing summary dashboard with stats, active resources, and recent activity\n *\n * This page now uses cached initial data from Apollo instead of independent queries,\n * reducing GraphQL calls from 5 to 0 for the initial render.\n * The refresh button triggers a refetch of the initial data.\n */\nexport function OverviewPage() {\n const handleNavigate = useVibeNavigate();\n const { t } = useI18n();\n const { hasPermission, isLoading: permissionsLoading } = usePermissions();\n\n const tr = (key: string, fallback: string, params?: Record<string, string | number>): string => {\n const translated = t(key, params);\n const template = translated === key ? fallback : translated;\n\n if (!params) return template;\n\n return Object.entries(params).reduce(\n (acc, [paramKey, paramValue]) => acc.replace(`{{${paramKey}}}`, String(paramValue)),\n template\n );\n };\n\n // Use cached initial data - reads from Apollo cache, no network request\n // Loading state reflects whether initial data has been loaded\n const {\n agents: cachedAgents,\n vibes: cachedVibes,\n sessions: cachedSessions,\n loading,\n error: dataError,\n totals,\n refetch,\n } = useCachedInitialData();\n\n // Targets is intentionally NOT part of VibeControlsInitialData (it caused a 25s\n // timeout there), so fetch the count with a lightweight standalone query — the\n // same pattern HealthPage uses.\n const { workspaceId } = useVibeControls();\n const targetsCountQuery = useListTargetsQuery({\n variables: { workspaceId: workspaceId || '', limit: 50 },\n skip: !workspaceId,\n fetchPolicy: 'cache-and-network',\n });\n\n const refetchTargets = targetsCountQuery.refetch;\n // The header Refresh button must refetch BOTH the cached initial data and the\n // standalone targets query — otherwise the Targets stat card stays stale after\n // a manual refresh.\n const handleRefresh = useCallback(() => {\n void refetch();\n void refetchTargets();\n }, [refetch, refetchTargets]);\n\n const targetItems: Array<{ status: string }> = targetsCountQuery.data?.targets.items ?? [];\n const targetsTotal = targetsCountQuery.data?.targets.total ?? 0;\n // `targetsConnected` is derived from `targetItems`, which is capped by the\n // query `limit`. When the workspace has more targets than the page size the\n // connected count would only reflect the first page and contradict the\n // server-side `targetsTotal`. Track truncation so we only surface the\n // connected detail when it is accurate for the full set.\n const targetsTruncated = targetsTotal > targetItems.length;\n const targetsConnected = targetItems.filter(\n (t) => t.status === 'connected' || t.status === 'active'\n ).length;\n\n if (dataError) {\n console.error('[overview] failed to load initial workspace data', dataError);\n }\n\n // Map cached data to component types\n // Using type assertion since we know the structure from the GraphQL query\n const agents = useMemo(() => {\n return cachedAgents.map((agent) => ({\n id: agent.id,\n workspaceId: '',\n name: agent.name,\n hostname: agent.hostname,\n platform: agent.platform,\n architecture: agent.architecture || '',\n version: agent.version || '',\n isActive: agent.isActive,\n status: agent.isActive ? ('ACTIVE' as const) : ('OFFLINE' as const),\n lastHeartbeat: agent.lastHeartbeat || null,\n tunnelUrl: agent.tunnelUrl ?? null,\n tunnelStatus: agent.tunnelStatus ?? null,\n agentApiKey: null,\n aiContext: null,\n configuration: {},\n capabilities: [],\n metadata: {},\n createdAt: agent.createdAt || '',\n updatedAt: '',\n }));\n }, [cachedAgents]);\n\n const vibes = useMemo(() => {\n return cachedVibes.map((vibe) => ({\n id: vibe.id,\n workspaceId: '',\n name: vibe.name,\n description: '',\n type: (vibe.type || 'PROJECT') as Vibe['type'],\n status: vibe.status as Vibe['status'],\n path: '',\n parentId: null,\n gitBranch: null,\n gitRemote: null,\n gitStatus: null,\n environmentVariables: {},\n tags: [],\n metadata: {},\n createdAt: '',\n updatedAt: '',\n archivedAt: null,\n // Multi-agent: surface attached agents so Overview cards can render a\n // count badge (\"+N agents\") or single name. Empty when none.\n agents: vibe.agents ?? [],\n }));\n }, [cachedVibes]);\n\n const sessions: Session[] = useMemo(() => {\n return cachedSessions.map((session) => ({\n id: session.id,\n workspaceId: '',\n agentId: session.agentId ?? null,\n vibeId: session.vibeId ?? null,\n name: session.name,\n type: (session.type || 'TERMINAL') as Session['type'],\n status: session.status as Session['status'],\n command: null,\n workingDirectory: null,\n environmentVariables: {},\n autoStart: false,\n pid: null,\n exitCode: null,\n lastOutput: null,\n metadata: {},\n createdAt: session.createdAt || '',\n updatedAt: '',\n agent: undefined,\n vibe: undefined,\n }));\n }, [cachedSessions]);\n\n // Calculate stats from cached totals\n const activeAgents = agents.filter((a) => a.isActive);\n const runningSessions = sessions.filter((s) => s.status === 'RUNNING');\n const activeVibes = vibes.filter((v) => v.status === 'ACTIVE');\n\n const stats = {\n agents: {\n total: totals.agents,\n active: activeAgents.length,\n offline: agents.filter((a) => !a.isActive).length,\n },\n vibes: {\n total: totals.vibes,\n active: activeVibes.length,\n archived: vibes.filter((v) => v.status === 'ARCHIVED').length,\n },\n sessions: {\n total: totals.sessions,\n running: runningSessions.length,\n stopped: sessions.filter((s) => s.status === 'STOPPED').length,\n },\n };\n\n // Recent activity - derive from session/agent data (simplified for now)\n const recentActivity = useMemo(() => {\n const activities: Array<{\n id: string;\n type: string;\n title: string;\n description: string;\n timestamp: string;\n color: string;\n }> = [];\n\n // Add recent sessions as activity\n sessions.slice(0, 3).forEach((session) => {\n activities.push({\n id: `session-${session.id}`,\n type: 'session_started',\n title:\n session.status === 'RUNNING'\n ? tr('overviewPage.activity.sessionRunning', 'Session Running')\n : tr('overviewPage.activity.sessionStatus', 'Session {{status}}', {\n status: session.status.toLowerCase(),\n }),\n description: `${session.name}`,\n timestamp: session.createdAt,\n color: session.status === 'RUNNING' ? 'green' : 'blue',\n });\n });\n\n // Add active agents as activity\n activeAgents.slice(0, 2).forEach((agent) => {\n activities.push({\n id: `agent-${agent.id}`,\n type: 'agent_connected',\n title: tr('overviewPage.activity.agentOnline', 'Agent Online'),\n description: tr('overviewPage.activity.agentOnlineDescription', '{{name}} is active', {\n name: agent.name,\n }),\n timestamp: agent.lastHeartbeat || agent.updatedAt,\n color: 'blue',\n });\n });\n\n // Sort by timestamp descending\n return activities\n .sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime())\n .slice(0, 6);\n }, [sessions, activeAgents]);\n\n if (\n permissionsLoading ||\n (loading &&\n cachedAgents.length === 0 &&\n cachedVibes.length === 0 &&\n cachedSessions.length === 0)\n )\n return (\n <div className=\"flex items-center justify-center min-h-[400px]\">\n <LoadingState message={tr('overviewPage.loadingPermissions', 'Loading permissions...')} />\n </div>\n );\n if (!hasPermission(VIBE_PERMISSIONS.VIBE_LIST))\n return (\n <div className=\"flex items-center justify-center min-h-[400px] px-4\">\n <div className=\"text-center space-y-2 max-w-sm\">\n <h2 className=\"text-lg font-semibold tracking-tight text-text-primary\">\n {tr('common.accessDeniedTitle', 'Access Denied')}\n </h2>\n <p className=\"text-sm text-text-secondary\">\n {tr(\n 'overviewPage.accessDeniedDescription',\n \"You don't have permission to access this page.\"\n )}\n </p>\n </div>\n </div>\n );\n\n const formatTimestamp = (timestamp: string) => {\n if (!timestamp) return tr('overviewPage.time.unknown', 'Unknown');\n const diff = Date.now() - new Date(timestamp).getTime();\n if (diff < 60000) return tr('overviewPage.time.justNow', 'Just now');\n if (diff < 3600000) {\n return tr('overviewPage.time.minutesAgo', '{{count}}m ago', {\n count: Math.floor(diff / 60000),\n });\n }\n if (diff < 86400000) {\n return tr('overviewPage.time.hoursAgo', '{{count}}h ago', {\n count: Math.floor(diff / 3600000),\n });\n }\n return tr('overviewPage.time.daysAgo', '{{count}}d ago', {\n count: Math.floor(diff / 86400000),\n });\n };\n\n const getActivityIcon = (type: string) => {\n const icons: Record<string, typeof Activity> = {\n session_started: Play,\n agent_connected: Server,\n vibe_updated: GitBranch,\n note_created: FileText,\n module_installed: Activity,\n };\n const Icon = icons[type] || Activity;\n return <Icon className=\"size-4\" />;\n };\n\n const getActivityColor = (color: string) => {\n const colors: Record<string, string> = {\n green: 'bg-status-success-bg-subtle text-status-success-text',\n blue: 'bg-[var(--color-accent-soft)] text-action-primary-bg',\n purple: 'bg-accent-purple-subtle text-accent-purple',\n orange: 'bg-status-warning-bg-subtle text-status-warning-text',\n cyan: 'bg-accent-teal-subtle text-accent-teal',\n yellow: 'bg-status-warning-bg-subtle text-status-warning-text',\n pink: 'bg-accent-purple-subtle text-accent-purple',\n };\n return colors[color] || 'bg-bg-sunken text-text-secondary';\n };\n\n return (\n <MotionPage className=\"space-y-6 px-6 lg:px-8 py-6 bg-bg-canvas min-h-screen\">\n <PageHeader\n title={tr('overviewPage.title', 'VibeControls Overview')}\n description={tr(\n 'overviewPage.description',\n 'Monitor and manage your development environments'\n )}\n actions={\n <button\n type=\"button\"\n onClick={handleRefresh}\n disabled={loading}\n className=\"inline-flex items-center gap-2 px-3 py-2 text-sm bg-bg-surface border border-border-subtle rounded-button text-text-primary hover:bg-bg-sunken hover:border-border-strong transition-all duration-200 disabled:opacity-50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus\"\n >\n <RefreshCw className={`size-4 ${loading ? 'animate-spin' : ''}`} aria-hidden=\"true\" />\n {tr('overviewPage.refresh', 'Refresh')}\n </button>\n }\n />\n\n {/* Friendly fallback when the initial workspace load fails — keeps the\n page header / refresh button rendered above so the user can recover\n without a hard reload. */}\n {dataError && !loading && (\n <ErrorState\n title={tr('overviewPage.loadFailed.title', 'Could not load workspace data')}\n message={tr(\n 'overviewPage.loadFailed.message',\n \"We couldn't load your VibeControls overview. Please try again in a moment.\"\n )}\n onRetry={handleRefresh}\n />\n )}\n\n {/* Agent CLI Setup Banner — shown when no agents exist */}\n {!dataError && agents.length === 0 && !loading && (\n <AgentSetupBanner variant=\"full\" dismissKey=\"vibecontrols-overview-setup-banner\" />\n )}\n\n {/* Stats Grid */}\n <MotionList\n data-tour=\"overview-stats\"\n className=\"grid grid-cols-2 sm:grid-cols-4 gap-4\"\n stagger={0.06}\n >\n <MotionItem variant=\"fadeScale\">\n <StatCard\n title={tr('overviewPage.stats.targets', 'Targets')}\n value={loading ? '-' : targetsTotal}\n subtitle={\n loading || targetsTruncated\n ? ''\n : tr('overviewPage.stats.connected', '{{count}} connected', {\n count: targetsConnected,\n })\n }\n icon={<Server className=\"size-6\" />}\n variant={!loading && targetsTotal > 0 ? 'success' : 'default'}\n onClick={() => handleNavigate('/targets')}\n />\n </MotionItem>\n <MotionItem variant=\"fadeScale\">\n <StatCard\n title={tr('overviewPage.stats.agents', 'Agents')}\n value={loading ? '-' : stats.agents.total}\n subtitle={\n loading\n ? ''\n : tr('overviewPage.stats.active', '{{count}} active', {\n count: stats.agents.active,\n })\n }\n icon={<Bot className=\"size-6\" />}\n variant={!loading && stats.agents.active > 0 ? 'success' : 'default'}\n onClick={() => handleNavigate('/agents')}\n />\n </MotionItem>\n <MotionItem variant=\"fadeScale\">\n <StatCard\n title={tr('overviewPage.stats.vibes', 'Vibes')}\n value={loading ? '-' : stats.vibes.total}\n subtitle={\n loading\n ? ''\n : tr('overviewPage.stats.active', '{{count}} active', {\n count: stats.vibes.active,\n })\n }\n icon={<Zap className=\"size-6\" />}\n onClick={() => handleNavigate('/vibes')}\n />\n </MotionItem>\n <MotionItem variant=\"fadeScale\">\n <StatCard\n title={tr('overviewPage.stats.sessions', 'Sessions')}\n value={loading ? '-' : stats.sessions.total}\n subtitle={\n loading\n ? ''\n : tr('overviewPage.stats.running', '{{count}} running', {\n count: stats.sessions.running,\n })\n }\n icon={<Terminal className=\"size-6\" />}\n variant={!loading && stats.sessions.running > 0 ? 'success' : 'default'}\n onClick={() => handleNavigate('/sessions')}\n />\n </MotionItem>\n </MotionList>\n\n {/* Main Content Grid */}\n <MotionList className=\"grid grid-cols-1 gap-6 xl:grid-cols-3\" stagger={0.08}>\n {/* Active Agents */}\n <MotionItem className=\"xl:col-span-2 h-full\">\n <Card className=\"flex h-full flex-col\">\n <div className=\"flex items-center justify-between mb-5\">\n <h2 className=\"text-lg font-semibold tracking-tight text-text-primary flex items-center gap-2.5\">\n <span className=\"inline-flex size-8 items-center justify-center rounded-lg bg-[var(--color-accent-soft)] text-action-primary-bg\">\n <Bot className=\"size-5\" />\n </span>\n {tr('overviewPage.sections.activeAgents', 'Active Agents')}\n </h2>\n <button\n type=\"button\"\n onClick={() => handleNavigate('/agents')}\n className=\"text-sm font-medium text-text-link hover:underline\"\n aria-label={tr('overviewPage.sections.viewAllAgents', 'View all agents')}\n >\n {tr('overviewPage.sections.viewAll', 'View all')}\n </button>\n </div>\n {activeAgents.length > 0 ? (\n <MotionList\n className=\"grid grid-cols-1 sm:grid-cols-2 gap-4 flex-1 content-start\"\n stagger={0.04}\n >\n {activeAgents.slice(0, 4).map((agent) => (\n <MotionItem key={agent.id}>\n <AgentCard agent={agent} onSelect={(a) => handleNavigate(`/agents/${a.id}`)} />\n </MotionItem>\n ))}\n </MotionList>\n ) : (\n <div className=\"flex flex-1 flex-col items-center justify-center text-center py-8 text-text-secondary\">\n <span className=\"inline-flex size-12 items-center justify-center rounded-full bg-[var(--color-accent-soft)] text-action-primary-bg mb-3\">\n <Bot className=\"size-6\" />\n </span>\n <p className=\"text-base font-semibold text-text-primary\">\n {tr('overviewPage.empty.noActiveAgents', 'No active agents')}\n </p>\n <p className=\"text-xs text-text-muted mt-1 mb-4\">\n {tr('overviewPage.empty.installCliHint', 'Install the agent CLI to get started')}\n </p>\n <div className=\"flex flex-wrap items-center justify-center gap-2\">\n <button\n type=\"button\"\n onClick={() => handleNavigate('/getting-started')}\n className=\"text-sm font-medium text-text-link hover:underline\"\n >\n {tr('overviewPage.empty.setupGuide', 'Setup guide')}\n </button>\n <span className=\"text-text-muted\">·</span>\n <button\n type=\"button\"\n onClick={() => handleNavigate('/agents')}\n className=\"text-sm text-text-link hover:underline\"\n >\n {tr('overviewPage.empty.addAgent', 'Add agent')}\n </button>\n </div>\n </div>\n )}\n </Card>\n </MotionItem>\n\n {/* Recent Activity */}\n <MotionItem className=\"h-full\">\n <Card className=\"flex h-full flex-col\">\n <div className=\"flex items-center justify-between mb-5\">\n <h2 className=\"text-lg font-semibold tracking-tight text-text-primary flex items-center gap-2.5\">\n <span className=\"inline-flex size-8 items-center justify-center rounded-lg bg-[var(--color-accent-soft)] text-action-primary-bg\">\n <Activity className=\"size-5\" />\n </span>\n {tr('overviewPage.sections.recentActivity', 'Recent Activity')}\n </h2>\n <button\n type=\"button\"\n onClick={() => handleNavigate('/audit')}\n className=\"text-sm font-medium text-text-link hover:underline\"\n aria-label={tr('overviewPage.sections.viewAllActivity', 'View all activity')}\n >\n View all\n </button>\n </div>\n <MotionList className=\"space-y-3 flex-1\" stagger={0.04}>\n {recentActivity.length > 0 ? (\n recentActivity.map((activity) => (\n <MotionItem key={activity.id} variant=\"fadeRight\">\n <div className=\"flex items-start gap-3\">\n <div\n className={`flex-shrink-0 size-8 rounded-full flex items-center justify-center ${getActivityColor(activity.color)}`}\n >\n {getActivityIcon(activity.type)}\n </div>\n <div className=\"flex-1 min-w-0\">\n <p className=\"text-sm font-medium text-text-primary truncate\">\n {activity.title}\n </p>\n <p className=\"text-xs text-text-secondary truncate\">\n {activity.description}\n </p>\n </div>\n <span className=\"text-xs text-text-secondary flex-shrink-0\">\n {formatTimestamp(activity.timestamp)}\n </span>\n </div>\n </MotionItem>\n ))\n ) : (\n <div className=\"flex h-full flex-col items-center justify-center text-center py-4 text-text-secondary text-sm\">\n <p>{tr('overviewPage.empty.noRecentActivity', 'No recent activity')}</p>\n <button\n type=\"button\"\n onClick={() => handleNavigate('/audit')}\n className=\"mt-2 text-sm text-text-link hover:underline\"\n >\n {tr('overviewPage.empty.viewAuditLog', 'View audit log')}\n </button>\n </div>\n )}\n </MotionList>\n </Card>\n </MotionItem>\n </MotionList>\n\n {/* Running Sessions */}\n <MotionItem>\n <Card>\n <div className=\"flex items-center justify-between mb-5\">\n <h2 className=\"text-lg font-semibold tracking-tight text-text-primary flex items-center gap-2.5\">\n <span className=\"inline-flex size-8 items-center justify-center rounded-lg bg-[var(--color-accent-soft)] text-action-primary-bg\">\n <Terminal className=\"size-5\" />\n </span>\n {tr('overviewPage.sections.runningSessions', 'Running Sessions')}\n </h2>\n <button\n type=\"button\"\n onClick={() => handleNavigate('/sessions')}\n className=\"text-sm font-medium text-text-link hover:underline\"\n >\n {tr('overviewPage.sections.viewAll', 'View all')}\n </button>\n </div>\n {runningSessions.length > 0 ? (\n <MotionList\n className=\"grid grid-cols-1 sm:grid-cols-2 xl:grid-cols-3 gap-4\"\n stagger={0.04}\n >\n {runningSessions.slice(0, 6).map((session) => (\n <MotionItem key={session.id}>\n <SessionCard\n session={session}\n onOpen={(s) => handleNavigate(`/sessions/${s.id}`)}\n />\n </MotionItem>\n ))}\n </MotionList>\n ) : (\n <div className=\"flex flex-col items-center text-center py-10 text-text-secondary\">\n <span className=\"inline-flex size-12 items-center justify-center rounded-full bg-[var(--color-accent-soft)] text-action-primary-bg mb-3\">\n <Terminal className=\"size-6\" />\n </span>\n <p className=\"text-base font-semibold text-text-primary\">\n {tr('overviewPage.empty.noRunningSessions', 'No running sessions')}\n </p>\n <button\n type=\"button\"\n onClick={() => handleNavigate('/sessions')}\n className=\"mt-3 text-sm font-medium text-text-link hover:underline\"\n >\n {tr('overviewPage.empty.startSession', 'Start a session')}\n </button>\n </div>\n )}\n </Card>\n </MotionItem>\n\n {/* Active Vibes */}\n <MotionItem>\n <Card>\n <div className=\"flex items-center justify-between mb-5\">\n <h2 className=\"text-lg font-semibold tracking-tight text-text-primary flex items-center gap-2.5\">\n <span className=\"inline-flex size-8 items-center justify-center rounded-lg bg-[var(--color-accent-soft)] text-action-primary-bg\">\n <Zap className=\"size-5\" />\n </span>\n {tr('overviewPage.sections.activeVibes', 'Active Vibes')}\n </h2>\n <button\n type=\"button\"\n onClick={() => handleNavigate('/vibes')}\n className=\"text-sm font-medium text-text-link hover:underline\"\n >\n {tr('overviewPage.sections.viewAll', 'View all')}\n </button>\n </div>\n {activeVibes.length > 0 ? (\n <MotionList\n className=\"grid grid-cols-1 sm:grid-cols-2 xl:grid-cols-3 gap-4\"\n stagger={0.04}\n >\n {activeVibes.slice(0, 6).map((vibe) => (\n <MotionItem key={vibe.id}>\n <VibeCard vibe={vibe} onOpen={(v) => handleNavigate(`/vibes/${v.id}`)} />\n </MotionItem>\n ))}\n </MotionList>\n ) : (\n <div className=\"flex flex-col items-center text-center py-10 text-text-secondary\">\n <span className=\"inline-flex size-12 items-center justify-center rounded-full bg-[var(--color-accent-soft)] text-action-primary-bg mb-3\">\n <Zap className=\"size-6\" />\n </span>\n <p className=\"text-base font-semibold text-text-primary\">\n {tr('overviewPage.empty.noActiveVibes', 'No active vibes')}\n </p>\n <button\n type=\"button\"\n onClick={() => handleNavigate('/vibes')}\n className=\"mt-3 text-sm font-medium text-text-link hover:underline\"\n >\n {tr('overviewPage.empty.addVibe', 'Add a vibe')}\n </button>\n </div>\n )}\n </Card>\n </MotionItem>\n </MotionPage>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"OverviewPage.js","names":[],"sources":["../../src/pages/OverviewPage.tsx"],"sourcesContent":["import { useCallback, useMemo } from 'react';\nimport { usePermissions } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { VIBE_PERMISSIONS } from '../constants/permissions';\nimport {\n Bot,\n Zap,\n Terminal,\n FileText,\n Activity,\n Server,\n Play,\n GitBranch,\n RefreshCw,\n} from 'lucide-react';\nimport { MotionPage, MotionList, MotionItem } from '@burdenoff/fe-libs/shared/motion';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { AgentCard } from '@/components/agents';\nimport { SessionCard } from '@/components/sessions';\nimport { VibeCard } from '@/components/vibes';\nimport {\n PageHeader,\n StatCard,\n Card,\n AgentSetupBanner,\n ErrorState,\n LoadingState,\n} from '@/components/shared';\nimport { useVibeNavigate, useVibeControls } from '@/providers/VibeControlsProvider';\nimport { useCachedInitialData } from '@/hooks/useCachedInitialData';\nimport { useListTargetsQuery } from '@/generated/wspace-operations';\nimport type { Vibe } from '@/components/vibes/types';\nimport type { Session } from '@/components/sessions/types';\n\n/**\n * Overview page showing summary dashboard with stats, active resources, and recent activity\n *\n * This page now uses cached initial data from Apollo instead of independent queries,\n * reducing GraphQL calls from 5 to 0 for the initial render.\n * The refresh button triggers a refetch of the initial data.\n */\nexport function OverviewPage() {\n const handleNavigate = useVibeNavigate();\n const { t } = useI18n();\n const { hasPermission, isLoading: permissionsLoading } = usePermissions();\n\n const tr = (key: string, fallback: string, params?: Record<string, string | number>): string => {\n const translated = t(key, params);\n const template = translated === key ? fallback : translated;\n\n if (!params) return template;\n\n return Object.entries(params).reduce(\n (acc, [paramKey, paramValue]) => acc.replace(`{{${paramKey}}}`, String(paramValue)),\n template\n );\n };\n\n // Use cached initial data - reads from Apollo cache, no network request\n // Loading state reflects whether initial data has been loaded\n const {\n agents: cachedAgents,\n vibes: cachedVibes,\n sessions: cachedSessions,\n loading,\n error: dataError,\n totals,\n refetch,\n } = useCachedInitialData();\n\n // Targets is intentionally NOT part of VibeControlsInitialData (it caused a 25s\n // timeout there), so fetch the count with a lightweight standalone query — the\n // same pattern HealthPage uses.\n const { workspaceId } = useVibeControls();\n const targetsCountQuery = useListTargetsQuery({\n variables: { workspaceId: workspaceId || '', limit: 50 },\n skip: !workspaceId,\n fetchPolicy: 'cache-and-network',\n });\n\n const refetchTargets = targetsCountQuery.refetch;\n // The header Refresh button must refetch BOTH the cached initial data and the\n // standalone targets query — otherwise the Targets stat card stays stale after\n // a manual refresh.\n const handleRefresh = useCallback(() => {\n void refetch();\n void refetchTargets();\n }, [refetch, refetchTargets]);\n\n const targetItems: Array<{ status: string }> = targetsCountQuery.data?.targets.items ?? [];\n const targetsTotal = targetsCountQuery.data?.targets.total ?? 0;\n // `targetsConnected` is derived from `targetItems`, which is capped by the\n // query `limit`. When the workspace has more targets than the page size the\n // connected count would only reflect the first page and contradict the\n // server-side `targetsTotal`. Track truncation so we only surface the\n // connected detail when it is accurate for the full set.\n const targetsTruncated = targetsTotal > targetItems.length;\n const targetsConnected = targetItems.filter(\n (t) => t.status === 'connected' || t.status === 'active'\n ).length;\n\n if (dataError) {\n console.error('[overview] failed to load initial workspace data', dataError);\n }\n\n // Map cached data to component types\n // Using type assertion since we know the structure from the GraphQL query\n const agents = useMemo(() => {\n return cachedAgents.map((agent) => ({\n id: agent.id,\n workspaceId: '',\n name: agent.name,\n hostname: agent.hostname,\n platform: agent.platform,\n architecture: agent.architecture || '',\n version: agent.version || '',\n isActive: agent.isActive,\n status: agent.isActive ? ('ACTIVE' as const) : ('OFFLINE' as const),\n lastHeartbeat: agent.lastHeartbeat || null,\n tunnelUrl: agent.tunnelUrl ?? null,\n tunnelStatus: agent.tunnelStatus ?? null,\n agentApiKey: null,\n aiContext: null,\n configuration: {},\n capabilities: [],\n metadata: {},\n createdAt: agent.createdAt || '',\n updatedAt: '',\n }));\n }, [cachedAgents]);\n\n const vibes = useMemo(() => {\n return cachedVibes.map((vibe) => ({\n id: vibe.id,\n workspaceId: '',\n name: vibe.name,\n description: '',\n type: (vibe.type || 'PROJECT') as Vibe['type'],\n status: vibe.status as Vibe['status'],\n path: '',\n parentId: null,\n gitBranch: null,\n gitRemote: null,\n gitStatus: null,\n environmentVariables: {},\n tags: [],\n metadata: {},\n createdAt: '',\n updatedAt: '',\n archivedAt: null,\n // Multi-agent: surface attached agents so Overview cards can render a\n // count badge (\"+N agents\") or single name. Empty when none.\n agents: vibe.agents ?? [],\n }));\n }, [cachedVibes]);\n\n const sessions: Session[] = useMemo(() => {\n return cachedSessions.map((session) => ({\n id: session.id,\n workspaceId: '',\n agentId: session.agentId ?? null,\n vibeId: session.vibeId ?? null,\n name: session.name,\n type: (session.type || 'TERMINAL') as Session['type'],\n status: session.status as Session['status'],\n command: null,\n workingDirectory: null,\n environmentVariables: {},\n autoStart: false,\n pid: null,\n exitCode: null,\n lastOutput: null,\n metadata: {},\n createdAt: session.createdAt || '',\n updatedAt: '',\n agent: undefined,\n vibe: undefined,\n }));\n }, [cachedSessions]);\n\n // Calculate stats from cached totals\n const activeAgents = agents.filter((a) => a.isActive);\n const runningSessions = sessions.filter((s) => s.status === 'RUNNING');\n const activeVibes = vibes.filter((v) => v.status === 'ACTIVE');\n\n const stats = {\n agents: {\n total: totals.agents,\n active: activeAgents.length,\n offline: agents.filter((a) => !a.isActive).length,\n },\n vibes: {\n total: totals.vibes,\n active: activeVibes.length,\n archived: vibes.filter((v) => v.status === 'ARCHIVED').length,\n },\n sessions: {\n total: totals.sessions,\n running: runningSessions.length,\n stopped: sessions.filter((s) => s.status === 'STOPPED').length,\n },\n };\n\n // Recent activity - derive from session/agent data (simplified for now)\n const recentActivity = useMemo(() => {\n const activities: Array<{\n id: string;\n type: string;\n title: string;\n description: string;\n timestamp: string;\n color: string;\n }> = [];\n\n // Add recent sessions as activity\n sessions.slice(0, 3).forEach((session) => {\n activities.push({\n id: `session-${session.id}`,\n type: 'session_started',\n title:\n session.status === 'RUNNING'\n ? tr('overviewPage.activity.sessionRunning', 'Session Running')\n : tr('overviewPage.activity.sessionStatus', 'Session {{status}}', {\n status: session.status.toLowerCase(),\n }),\n description: `${session.name}`,\n timestamp: session.createdAt,\n color: session.status === 'RUNNING' ? 'green' : 'blue',\n });\n });\n\n // Add active agents as activity\n activeAgents.slice(0, 2).forEach((agent) => {\n activities.push({\n id: `agent-${agent.id}`,\n type: 'agent_connected',\n title: tr('overviewPage.activity.agentOnline', 'Agent Online'),\n description: tr('overviewPage.activity.agentOnlineDescription', '{{name}} is active', {\n name: agent.name,\n }),\n timestamp: agent.lastHeartbeat || agent.updatedAt,\n color: 'blue',\n });\n });\n\n // Sort by timestamp descending\n return activities\n .sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime())\n .slice(0, 6);\n }, [sessions, activeAgents]);\n\n if (\n permissionsLoading ||\n (loading &&\n cachedAgents.length === 0 &&\n cachedVibes.length === 0 &&\n cachedSessions.length === 0)\n )\n return (\n <div className=\"flex items-center justify-center min-h-[400px]\">\n <LoadingState message={tr('overviewPage.loadingPermissions', 'Loading permissions...')} />\n </div>\n );\n if (!hasPermission(VIBE_PERMISSIONS.VIBE_LIST))\n return (\n <div className=\"flex items-center justify-center min-h-[400px] px-4\">\n <div className=\"text-center space-y-2 max-w-sm\">\n <h2 className=\"text-lg font-semibold tracking-tight text-text-primary\">\n {tr('common.accessDeniedTitle', 'Access Denied')}\n </h2>\n <p className=\"text-sm text-text-secondary\">\n {tr(\n 'overviewPage.accessDeniedDescription',\n \"You don't have permission to access this page.\"\n )}\n </p>\n </div>\n </div>\n );\n\n const formatTimestamp = (timestamp: string) => {\n if (!timestamp) return tr('overviewPage.time.unknown', 'Unknown');\n const diff = Date.now() - new Date(timestamp).getTime();\n if (diff < 60000) return tr('overviewPage.time.justNow', 'Just now');\n if (diff < 3600000) {\n return tr('overviewPage.time.minutesAgo', '{{count}}m ago', {\n count: Math.floor(diff / 60000),\n });\n }\n if (diff < 86400000) {\n return tr('overviewPage.time.hoursAgo', '{{count}}h ago', {\n count: Math.floor(diff / 3600000),\n });\n }\n return tr('overviewPage.time.daysAgo', '{{count}}d ago', {\n count: Math.floor(diff / 86400000),\n });\n };\n\n const getActivityIcon = (type: string) => {\n const icons: Record<string, typeof Activity> = {\n session_started: Play,\n agent_connected: Server,\n vibe_updated: GitBranch,\n note_created: FileText,\n module_installed: Activity,\n };\n const Icon = icons[type] || Activity;\n return <Icon className=\"size-4\" />;\n };\n\n const getActivityColor = (color: string) => {\n const colors: Record<string, string> = {\n green: 'bg-status-success-bg-subtle text-status-success-text',\n blue: 'bg-[var(--color-accent-soft)] text-text-primary',\n purple: 'bg-accent-purple-subtle text-text-primary',\n orange: 'bg-status-warning-bg-subtle text-status-warning-text',\n cyan: 'bg-accent-teal-subtle text-accent-teal',\n yellow: 'bg-status-warning-bg-subtle text-status-warning-text',\n pink: 'bg-accent-purple-subtle text-text-primary',\n };\n return colors[color] || 'bg-bg-sunken text-text-secondary';\n };\n\n return (\n <MotionPage className=\"space-y-6 px-6 lg:px-8 py-6 bg-bg-canvas min-h-screen\">\n <PageHeader\n title={tr('overviewPage.title', 'VibeControls Overview')}\n description={tr(\n 'overviewPage.description',\n 'Monitor and manage your development environments'\n )}\n actions={\n <button\n type=\"button\"\n onClick={handleRefresh}\n disabled={loading}\n className=\"inline-flex items-center gap-2 px-3 py-2 text-sm bg-bg-surface border border-border-subtle rounded-button text-text-primary hover:bg-bg-sunken hover:border-border-strong transition-all duration-200 disabled:opacity-50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus\"\n >\n <RefreshCw className={`size-4 ${loading ? 'animate-spin' : ''}`} aria-hidden=\"true\" />\n {tr('overviewPage.refresh', 'Refresh')}\n </button>\n }\n />\n\n {/* Friendly fallback when the initial workspace load fails — keeps the\n page header / refresh button rendered above so the user can recover\n without a hard reload. */}\n {dataError && !loading && (\n <ErrorState\n title={tr('overviewPage.loadFailed.title', 'Could not load workspace data')}\n message={tr(\n 'overviewPage.loadFailed.message',\n \"We couldn't load your VibeControls overview. Please try again in a moment.\"\n )}\n onRetry={handleRefresh}\n />\n )}\n\n {/* Agent CLI Setup Banner — shown when no agents exist */}\n {!dataError && agents.length === 0 && !loading && (\n <AgentSetupBanner variant=\"full\" dismissKey=\"vibecontrols-overview-setup-banner\" />\n )}\n\n {/* Stats Grid */}\n <MotionList\n data-tour=\"overview-stats\"\n className=\"grid grid-cols-2 sm:grid-cols-4 gap-4\"\n stagger={0.06}\n >\n <MotionItem variant=\"fadeScale\">\n <StatCard\n title={tr('overviewPage.stats.targets', 'Targets')}\n value={loading ? '-' : targetsTotal}\n subtitle={\n loading || targetsTruncated\n ? ''\n : tr('overviewPage.stats.connected', '{{count}} connected', {\n count: targetsConnected,\n })\n }\n icon={<Server className=\"size-6\" />}\n variant={!loading && targetsTotal > 0 ? 'success' : 'default'}\n onClick={() => handleNavigate('/targets')}\n />\n </MotionItem>\n <MotionItem variant=\"fadeScale\">\n <StatCard\n title={tr('overviewPage.stats.agents', 'Agents')}\n value={loading ? '-' : stats.agents.total}\n subtitle={\n loading\n ? ''\n : tr('overviewPage.stats.active', '{{count}} active', {\n count: stats.agents.active,\n })\n }\n icon={<Bot className=\"size-6\" />}\n variant={!loading && stats.agents.active > 0 ? 'success' : 'default'}\n onClick={() => handleNavigate('/agents')}\n />\n </MotionItem>\n <MotionItem variant=\"fadeScale\">\n <StatCard\n title={tr('overviewPage.stats.vibes', 'Vibes')}\n value={loading ? '-' : stats.vibes.total}\n subtitle={\n loading\n ? ''\n : tr('overviewPage.stats.active', '{{count}} active', {\n count: stats.vibes.active,\n })\n }\n icon={<Zap className=\"size-6\" />}\n onClick={() => handleNavigate('/vibes')}\n />\n </MotionItem>\n <MotionItem variant=\"fadeScale\">\n <StatCard\n title={tr('overviewPage.stats.sessions', 'Sessions')}\n value={loading ? '-' : stats.sessions.total}\n subtitle={\n loading\n ? ''\n : tr('overviewPage.stats.running', '{{count}} running', {\n count: stats.sessions.running,\n })\n }\n icon={<Terminal className=\"size-6\" />}\n variant={!loading && stats.sessions.running > 0 ? 'success' : 'default'}\n onClick={() => handleNavigate('/sessions')}\n />\n </MotionItem>\n </MotionList>\n\n {/* Main Content Grid */}\n <MotionList className=\"grid grid-cols-1 gap-6 xl:grid-cols-3\" stagger={0.08}>\n {/* Active Agents */}\n <MotionItem className=\"xl:col-span-2 h-full\">\n <Card className=\"flex h-full flex-col\">\n <div className=\"flex items-center justify-between mb-5\">\n <h2 className=\"text-lg font-semibold tracking-tight text-text-primary flex items-center gap-2.5\">\n <span className=\"inline-flex size-8 items-center justify-center rounded-lg bg-[var(--color-accent-soft)] text-text-primary\">\n <Bot className=\"size-5\" />\n </span>\n {tr('overviewPage.sections.activeAgents', 'Active Agents')}\n </h2>\n <button\n type=\"button\"\n onClick={() => handleNavigate('/agents')}\n className=\"text-sm font-medium text-text-link hover:underline\"\n aria-label={tr('overviewPage.sections.viewAllAgents', 'View all agents')}\n >\n {tr('overviewPage.sections.viewAll', 'View all')}\n </button>\n </div>\n {activeAgents.length > 0 ? (\n <MotionList\n className=\"grid grid-cols-1 sm:grid-cols-2 gap-4 flex-1 content-start\"\n stagger={0.04}\n >\n {activeAgents.slice(0, 4).map((agent) => (\n <MotionItem key={agent.id}>\n <AgentCard agent={agent} onSelect={(a) => handleNavigate(`/agents/${a.id}`)} />\n </MotionItem>\n ))}\n </MotionList>\n ) : (\n <div className=\"flex flex-1 flex-col items-center justify-center text-center py-8 text-text-secondary\">\n <span className=\"inline-flex size-12 items-center justify-center rounded-full bg-[var(--color-accent-soft)] text-text-primary mb-3\">\n <Bot className=\"size-6\" />\n </span>\n <p className=\"text-base font-semibold text-text-primary\">\n {tr('overviewPage.empty.noActiveAgents', 'No active agents')}\n </p>\n <p className=\"text-xs text-text-muted mt-1 mb-4\">\n {tr('overviewPage.empty.installCliHint', 'Install the agent CLI to get started')}\n </p>\n <div className=\"flex flex-wrap items-center justify-center gap-2\">\n <button\n type=\"button\"\n onClick={() => handleNavigate('/getting-started')}\n className=\"text-sm font-medium text-text-link hover:underline\"\n >\n {tr('overviewPage.empty.setupGuide', 'Setup guide')}\n </button>\n <span className=\"text-text-muted\">·</span>\n <button\n type=\"button\"\n onClick={() => handleNavigate('/agents')}\n className=\"text-sm text-text-link hover:underline\"\n >\n {tr('overviewPage.empty.addAgent', 'Add agent')}\n </button>\n </div>\n </div>\n )}\n </Card>\n </MotionItem>\n\n {/* Recent Activity */}\n <MotionItem className=\"h-full\">\n <Card className=\"flex h-full flex-col\">\n <div className=\"flex items-center justify-between mb-5\">\n <h2 className=\"text-lg font-semibold tracking-tight text-text-primary flex items-center gap-2.5\">\n <span className=\"inline-flex size-8 items-center justify-center rounded-lg bg-[var(--color-accent-soft)] text-text-primary\">\n <Activity className=\"size-5\" />\n </span>\n {tr('overviewPage.sections.recentActivity', 'Recent Activity')}\n </h2>\n <button\n type=\"button\"\n onClick={() => handleNavigate('/audit')}\n className=\"text-sm font-medium text-text-link hover:underline\"\n aria-label={tr('overviewPage.sections.viewAllActivity', 'View all activity')}\n >\n View all\n </button>\n </div>\n <MotionList className=\"space-y-3 flex-1\" stagger={0.04}>\n {recentActivity.length > 0 ? (\n recentActivity.map((activity) => (\n <MotionItem key={activity.id} variant=\"fadeRight\">\n <div className=\"flex items-start gap-3\">\n <div\n className={`flex-shrink-0 size-8 rounded-full flex items-center justify-center ${getActivityColor(activity.color)}`}\n >\n {getActivityIcon(activity.type)}\n </div>\n <div className=\"flex-1 min-w-0\">\n <p className=\"text-sm font-medium text-text-primary truncate\">\n {activity.title}\n </p>\n <p className=\"text-xs text-text-secondary truncate\">\n {activity.description}\n </p>\n </div>\n <span className=\"text-xs text-text-secondary flex-shrink-0\">\n {formatTimestamp(activity.timestamp)}\n </span>\n </div>\n </MotionItem>\n ))\n ) : (\n <div className=\"flex h-full flex-col items-center justify-center text-center py-4 text-text-secondary text-sm\">\n <p>{tr('overviewPage.empty.noRecentActivity', 'No recent activity')}</p>\n <button\n type=\"button\"\n onClick={() => handleNavigate('/audit')}\n className=\"mt-2 text-sm text-text-link hover:underline\"\n >\n {tr('overviewPage.empty.viewAuditLog', 'View audit log')}\n </button>\n </div>\n )}\n </MotionList>\n </Card>\n </MotionItem>\n </MotionList>\n\n {/* Running Sessions */}\n <MotionItem>\n <Card>\n <div className=\"flex items-center justify-between mb-5\">\n <h2 className=\"text-lg font-semibold tracking-tight text-text-primary flex items-center gap-2.5\">\n <span className=\"inline-flex size-8 items-center justify-center rounded-lg bg-[var(--color-accent-soft)] text-text-primary\">\n <Terminal className=\"size-5\" />\n </span>\n {tr('overviewPage.sections.runningSessions', 'Running Sessions')}\n </h2>\n <button\n type=\"button\"\n onClick={() => handleNavigate('/sessions')}\n className=\"text-sm font-medium text-text-link hover:underline\"\n >\n {tr('overviewPage.sections.viewAll', 'View all')}\n </button>\n </div>\n {runningSessions.length > 0 ? (\n <MotionList\n className=\"grid grid-cols-1 sm:grid-cols-2 xl:grid-cols-3 gap-4\"\n stagger={0.04}\n >\n {runningSessions.slice(0, 6).map((session) => (\n <MotionItem key={session.id}>\n <SessionCard\n session={session}\n onOpen={(s) => handleNavigate(`/sessions/${s.id}`)}\n />\n </MotionItem>\n ))}\n </MotionList>\n ) : (\n <div className=\"flex flex-col items-center text-center py-10 text-text-secondary\">\n <span className=\"inline-flex size-12 items-center justify-center rounded-full bg-[var(--color-accent-soft)] text-text-primary mb-3\">\n <Terminal className=\"size-6\" />\n </span>\n <p className=\"text-base font-semibold text-text-primary\">\n {tr('overviewPage.empty.noRunningSessions', 'No running sessions')}\n </p>\n <button\n type=\"button\"\n onClick={() => handleNavigate('/sessions')}\n className=\"mt-3 text-sm font-medium text-text-link hover:underline\"\n >\n {tr('overviewPage.empty.startSession', 'Start a session')}\n </button>\n </div>\n )}\n </Card>\n </MotionItem>\n\n {/* Active Vibes */}\n <MotionItem>\n <Card>\n <div className=\"flex items-center justify-between mb-5\">\n <h2 className=\"text-lg font-semibold tracking-tight text-text-primary flex items-center gap-2.5\">\n <span className=\"inline-flex size-8 items-center justify-center rounded-lg bg-[var(--color-accent-soft)] text-text-primary\">\n <Zap className=\"size-5\" />\n </span>\n {tr('overviewPage.sections.activeVibes', 'Active Vibes')}\n </h2>\n <button\n type=\"button\"\n onClick={() => handleNavigate('/vibes')}\n className=\"text-sm font-medium text-text-link hover:underline\"\n >\n {tr('overviewPage.sections.viewAll', 'View all')}\n </button>\n </div>\n {activeVibes.length > 0 ? (\n <MotionList\n className=\"grid grid-cols-1 sm:grid-cols-2 xl:grid-cols-3 gap-4\"\n stagger={0.04}\n >\n {activeVibes.slice(0, 6).map((vibe) => (\n <MotionItem key={vibe.id}>\n <VibeCard vibe={vibe} onOpen={(v) => handleNavigate(`/vibes/${v.id}`)} />\n </MotionItem>\n ))}\n </MotionList>\n ) : (\n <div className=\"flex flex-col items-center text-center py-10 text-text-secondary\">\n <span className=\"inline-flex size-12 items-center justify-center rounded-full bg-[var(--color-accent-soft)] text-text-primary mb-3\">\n <Zap className=\"size-6\" />\n </span>\n <p className=\"text-base font-semibold text-text-primary\">\n {tr('overviewPage.empty.noActiveVibes', 'No active vibes')}\n </p>\n <button\n type=\"button\"\n onClick={() => handleNavigate('/vibes')}\n className=\"mt-3 text-sm font-medium text-text-link hover:underline\"\n >\n {tr('overviewPage.empty.addVibe', 'Add a vibe')}\n </button>\n </div>\n )}\n </Card>\n </MotionItem>\n </MotionPage>\n );\n}\n"],"mappings":"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"}
|
|
@@ -88,7 +88,7 @@ function v(e) {
|
|
|
88
88
|
}
|
|
89
89
|
function y(e) {
|
|
90
90
|
switch (e) {
|
|
91
|
-
case "Meta": return "bg-accent-purple-subtle text-
|
|
91
|
+
case "Meta": return "bg-accent-purple-subtle text-text-primary border-accent-purple/30";
|
|
92
92
|
case "Provider": return "bg-status-info-bg text-status-info-text border-status-info-border";
|
|
93
93
|
default: return "bg-bg-sunken text-text-secondary border-border-default";
|
|
94
94
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PluginsCatalogPage.js","names":[],"sources":["../../src/pages/PluginsCatalogPage.tsx"],"sourcesContent":["import { useMemo, useState, useCallback } from 'react';\nimport { Check, ChevronDown, Copy, Package, Plug, Search, X } from 'lucide-react';\nimport { MotionPage } from '@burdenoff/fe-libs/shared/motion';\nimport { PageContainer, Card } from '@burdenoff/fe-libs/ui';\nimport { PLUGIN_CATALOG, type PluginMeta } from '@/constants/pluginCatalog';\nimport { useTr } from '@/shared/hooks/useTr';\n\ntype PluginType = 'Meta' | 'Provider' | 'Plugin';\n\ninterface CategoryOption {\n value: string;\n labelKey: string;\n labelFallback: string;\n}\n\nconst CATEGORY_OPTIONS: CategoryOption[] = [\n { value: 'all', labelKey: 'vibecontrols.plugins.category.all', labelFallback: 'All' },\n { value: 'ai-core', labelKey: 'vibecontrols.plugins.category.aiCore', labelFallback: 'AI Core' },\n {\n value: 'ai-provider',\n labelKey: 'vibecontrols.plugins.category.aiProvider',\n labelFallback: 'AI Providers',\n },\n {\n value: 'plan-core',\n labelKey: 'vibecontrols.plugins.category.planCore',\n labelFallback: 'Plan Core',\n },\n {\n value: 'plan-provider',\n labelKey: 'vibecontrols.plugins.category.planProvider',\n labelFallback: 'Plan Providers',\n },\n {\n value: 'session-core',\n labelKey: 'vibecontrols.plugins.category.sessionCore',\n labelFallback: 'Session Core',\n },\n {\n value: 'session-provider',\n labelKey: 'vibecontrols.plugins.category.sessionProvider',\n labelFallback: 'Session Providers',\n },\n { value: 'tunnel', labelKey: 'vibecontrols.plugins.category.tunnel', labelFallback: 'Tunnel' },\n { value: 'vcs', labelKey: 'vibecontrols.plugins.category.vcs', labelFallback: 'VCS' },\n {\n value: 'api-playground',\n labelKey: 'vibecontrols.plugins.category.apiPlayground',\n labelFallback: 'API Playground',\n },\n {\n value: 'gitops-core',\n labelKey: 'vibecontrols.plugins.category.gitopsCore',\n labelFallback: 'GitOps Core',\n },\n {\n value: 'gitops-provider',\n labelKey: 'vibecontrols.plugins.category.gitopsProvider',\n labelFallback: 'GitOps Providers',\n },\n {\n value: 'security-core',\n labelKey: 'vibecontrols.plugins.category.securityCore',\n labelFallback: 'Security Core',\n },\n {\n value: 'security-provider',\n labelKey: 'vibecontrols.plugins.category.securityProvider',\n labelFallback: 'Security Providers',\n },\n { value: 'tool', labelKey: 'vibecontrols.plugins.category.tool', labelFallback: 'Tools' },\n];\n\nfunction derivePluginType(category: PluginMeta['category']): PluginType {\n if (category.endsWith('-core')) return 'Meta';\n if (category.endsWith('-provider')) return 'Provider';\n return 'Plugin';\n}\n\nfunction typeBadgeClasses(type: PluginType): string {\n switch (type) {\n case 'Meta':\n return 'bg-accent-purple-subtle text-accent-purple border-accent-purple/30';\n case 'Provider':\n return 'bg-status-info-bg text-status-info-text border-status-info-border';\n default:\n return 'bg-bg-sunken text-text-secondary border-border-default';\n }\n}\n\ninterface CopyButtonProps {\n text: string;\n ariaLabel: string;\n}\n\nfunction CopyButton({ text, ariaLabel }: CopyButtonProps) {\n const [copied, setCopied] = useState(false);\n const handleCopy = useCallback(async () => {\n try {\n if (typeof navigator !== 'undefined' && navigator.clipboard) {\n await navigator.clipboard.writeText(text);\n }\n setCopied(true);\n window.setTimeout(() => setCopied(false), 1500);\n } catch {\n setCopied(false);\n }\n }, [text]);\n\n return (\n <button\n type=\"button\"\n onClick={handleCopy}\n aria-label={ariaLabel}\n className=\"inline-flex items-center justify-center size-7 rounded border border-border-default bg-bg-surface text-text-secondary hover:text-text-primary hover:bg-bg-elevated transition-colors flex-shrink-0\"\n >\n {copied ? <Check className=\"size-3.5\" /> : <Copy className=\"size-3.5\" />}\n </button>\n );\n}\n\ninterface PluginCardProps {\n plugin: PluginMeta;\n}\n\nfunction PluginCard({ plugin }: PluginCardProps) {\n const tr = useTr();\n const type = derivePluginType(plugin.category);\n const installCommand = `npm install ${plugin.packageName}`;\n const usageSnippet = `import plugin from '${plugin.packageName}';`;\n\n const [showUsage, setShowUsage] = useState(false);\n const [showCommands, setShowCommands] = useState(false);\n\n return (\n <Card className=\"flex flex-col h-full p-0 bg-bg-surface border-border-default\">\n <div className=\"flex flex-col gap-3 p-4\">\n <div className=\"flex items-start justify-between gap-3\">\n <div className=\"min-w-0 flex-1\">\n <h3 className=\"text-base font-semibold text-text-primary truncate\">\n {plugin.displayName}\n </h3>\n <p className=\"text-xs text-text-secondary font-mono truncate mt-1\">\n {plugin.packageName}\n </p>\n </div>\n <span\n className={`inline-flex items-center gap-1 px-2 py-0.5 text-[10px] uppercase tracking-wide font-semibold rounded-full border ${typeBadgeClasses(type)} flex-shrink-0`}\n >\n {type}\n </span>\n </div>\n\n <p className=\"text-sm text-text-secondary leading-snug\">{plugin.description}</p>\n\n <div className=\"mt-1 space-y-1.5\">\n <p className=\"text-[11px] font-medium uppercase tracking-wide text-text-secondary\">\n {tr('vibecontrols.plugins.install', 'Install')}\n </p>\n <div className=\"flex items-center gap-2 rounded-md border border-border-default bg-bg-sunken px-2.5 py-1.5\">\n <code className=\"flex-1 text-xs font-mono text-text-primary truncate\">\n {installCommand}\n </code>\n <CopyButton\n text={installCommand}\n ariaLabel={tr(\n 'vibecontrols.plugins.copyInstall',\n 'Copy install command for {{name}}',\n {\n name: plugin.displayName,\n }\n )}\n />\n </div>\n </div>\n\n <div className=\"space-y-2\">\n <button\n type=\"button\"\n onClick={() => setShowUsage((value) => !value)}\n className=\"w-full flex items-center justify-between gap-2 text-left text-xs font-medium text-text-secondary hover:text-text-primary transition-colors\"\n aria-expanded={showUsage}\n >\n <span>{tr('vibecontrols.plugins.usage', 'Usage')}</span>\n <ChevronDown\n className={`size-3.5 transition-transform ${showUsage ? 'rotate-180' : ''}`}\n />\n </button>\n {showUsage && (\n <div className=\"flex items-start gap-2 rounded-md border border-border-default bg-bg-sunken px-2.5 py-2\">\n <code className=\"flex-1 text-xs font-mono text-text-primary whitespace-pre-wrap break-all\">\n {usageSnippet}\n </code>\n <CopyButton\n text={usageSnippet}\n ariaLabel={tr('vibecontrols.plugins.copyUsage', 'Copy usage snippet for {{name}}', {\n name: plugin.displayName,\n })}\n />\n </div>\n )}\n </div>\n\n <div className=\"space-y-2\">\n <button\n type=\"button\"\n onClick={() => setShowCommands((value) => !value)}\n className=\"w-full flex items-center justify-between gap-2 text-left text-xs font-medium text-text-secondary hover:text-text-primary transition-colors\"\n aria-expanded={showCommands}\n >\n <span>{tr('vibecontrols.plugins.commands', 'Command reference')}</span>\n <ChevronDown\n className={`size-3.5 transition-transform ${showCommands ? 'rotate-180' : ''}`}\n />\n </button>\n {showCommands && (\n <div className=\"rounded-md border border-border-default bg-bg-sunken px-2.5 py-2\">\n <p className=\"text-xs text-text-secondary\">\n {tr(\n 'vibecontrols.plugins.commandsHint',\n 'See the agent admin sub-panel or the plugin README for the documented command list.'\n )}\n </p>\n </div>\n )}\n </div>\n </div>\n </Card>\n );\n}\n\nexport function PluginsCatalogPage() {\n const tr = useTr();\n const [search, setSearch] = useState('');\n const [activeCategory, setActiveCategory] = useState<string>('all');\n\n const pluginList = useMemo(() => {\n return Object.values(PLUGIN_CATALOG).sort((a, b) => a.displayName.localeCompare(b.displayName));\n }, []);\n\n const filteredPlugins = useMemo(() => {\n const term = search.trim().toLowerCase();\n return pluginList.filter((plugin) => {\n if (activeCategory !== 'all' && plugin.category !== activeCategory) return false;\n if (!term) return true;\n const haystack =\n `${plugin.displayName} ${plugin.description} ${plugin.packageName}`.toLowerCase();\n return haystack.includes(term);\n });\n }, [pluginList, search, activeCategory]);\n\n return (\n <PageContainer padded={false}>\n <MotionPage className=\"flex flex-col h-full\">\n {/* Sticky header with search + filter chips */}\n <div className=\"sticky top-0 z-10 border-b border-border-subtle bg-bg-surface px-6 lg:px-8 pt-5 pb-4 space-y-3\">\n <div className=\"flex items-start gap-3\">\n <div className=\"flex size-9 flex-shrink-0 items-center justify-center rounded-lg bg-[var(--color-accent-soft)]\">\n <Plug className=\"size-5 text-action-primary-bg\" />\n </div>\n <div className=\"min-w-0 flex-1\">\n <h1 className=\"text-2xl font-semibold tracking-tight text-text-primary\">\n {tr('vibecontrols.plugins.title', 'Plugins')}\n </h1>\n <p className=\"text-sm text-text-secondary mt-0.5\">\n {tr(\n 'vibecontrols.plugins.description',\n 'Browse and install plugins to extend your VibeControls agents.'\n )}\n </p>\n </div>\n </div>\n\n <div className=\"relative\">\n <Search className=\"absolute left-3 top-1/2 -translate-y-1/2 size-4 text-text-secondary\" />\n <input\n type=\"search\"\n value={search}\n onChange={(event) => setSearch(event.target.value)}\n placeholder={tr(\n 'vibecontrols.plugins.searchPlaceholder',\n 'Search by name, description, or package…'\n )}\n aria-label={tr('vibecontrols.plugins.searchLabel', 'Search plugins')}\n className=\"w-full pl-10 pr-10 py-2 text-sm bg-bg-sunken border border-border-default rounded-md text-text-primary placeholder:text-text-placeholder focus:outline-none focus:ring-2 focus:ring-action-primary-bg focus:border-transparent transition-all\"\n />\n {search && (\n <button\n type=\"button\"\n onClick={() => setSearch('')}\n aria-label={tr('vibecontrols.plugins.clearSearch', 'Clear search')}\n className=\"absolute right-3 top-1/2 -translate-y-1/2 p-0.5 rounded hover:bg-bg-elevated transition-colors\"\n >\n <X className=\"size-4 text-text-secondary\" />\n </button>\n )}\n </div>\n\n <div\n className=\"flex flex-wrap gap-2\"\n role=\"tablist\"\n aria-label={tr('vibecontrols.plugins.categories', 'Plugin categories')}\n >\n {CATEGORY_OPTIONS.map((option) => {\n const isActive = activeCategory === option.value;\n return (\n <button\n key={option.value}\n type=\"button\"\n role=\"tab\"\n aria-selected={isActive}\n onClick={() => setActiveCategory(option.value)}\n className={`px-3 py-1 text-xs rounded-full border transition-colors ${\n isActive\n ? 'bg-action-primary-bg text-action-primary-text border-action-primary-bg'\n : 'bg-bg-surface text-text-secondary border-border-default hover:bg-bg-sunken hover:text-text-primary'\n }`}\n >\n {tr(option.labelKey, option.labelFallback)}\n </button>\n );\n })}\n </div>\n </div>\n\n {/* Grid body */}\n <div className=\"flex-1 px-4 sm:px-6 py-4\">\n {filteredPlugins.length === 0 ? (\n <div className=\"flex flex-col items-center justify-center text-center py-16 text-text-secondary\">\n <Package className=\"size-12 mb-3 opacity-50\" />\n <p className=\"text-sm font-medium text-text-primary\">\n {tr('vibecontrols.plugins.empty.title', 'No matching plugins')}\n </p>\n <p className=\"text-xs mt-1\">\n {tr(\n 'vibecontrols.plugins.empty.description',\n 'Try a different search term or category.'\n )}\n </p>\n </div>\n ) : (\n <div className=\"grid gap-3 grid-cols-1 sm:grid-cols-2 xl:grid-cols-3\">\n {filteredPlugins.map((plugin) => (\n <PluginCard key={plugin.packageName} plugin={plugin} />\n ))}\n </div>\n )}\n </div>\n </MotionPage>\n </PageContainer>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"PluginsCatalogPage.js","names":[],"sources":["../../src/pages/PluginsCatalogPage.tsx"],"sourcesContent":["import { useMemo, useState, useCallback } from 'react';\nimport { Check, ChevronDown, Copy, Package, Plug, Search, X } from 'lucide-react';\nimport { MotionPage } from '@burdenoff/fe-libs/shared/motion';\nimport { PageContainer, Card } from '@burdenoff/fe-libs/ui';\nimport { PLUGIN_CATALOG, type PluginMeta } from '@/constants/pluginCatalog';\nimport { useTr } from '@/shared/hooks/useTr';\n\ntype PluginType = 'Meta' | 'Provider' | 'Plugin';\n\ninterface CategoryOption {\n value: string;\n labelKey: string;\n labelFallback: string;\n}\n\nconst CATEGORY_OPTIONS: CategoryOption[] = [\n { value: 'all', labelKey: 'vibecontrols.plugins.category.all', labelFallback: 'All' },\n { value: 'ai-core', labelKey: 'vibecontrols.plugins.category.aiCore', labelFallback: 'AI Core' },\n {\n value: 'ai-provider',\n labelKey: 'vibecontrols.plugins.category.aiProvider',\n labelFallback: 'AI Providers',\n },\n {\n value: 'plan-core',\n labelKey: 'vibecontrols.plugins.category.planCore',\n labelFallback: 'Plan Core',\n },\n {\n value: 'plan-provider',\n labelKey: 'vibecontrols.plugins.category.planProvider',\n labelFallback: 'Plan Providers',\n },\n {\n value: 'session-core',\n labelKey: 'vibecontrols.plugins.category.sessionCore',\n labelFallback: 'Session Core',\n },\n {\n value: 'session-provider',\n labelKey: 'vibecontrols.plugins.category.sessionProvider',\n labelFallback: 'Session Providers',\n },\n { value: 'tunnel', labelKey: 'vibecontrols.plugins.category.tunnel', labelFallback: 'Tunnel' },\n { value: 'vcs', labelKey: 'vibecontrols.plugins.category.vcs', labelFallback: 'VCS' },\n {\n value: 'api-playground',\n labelKey: 'vibecontrols.plugins.category.apiPlayground',\n labelFallback: 'API Playground',\n },\n {\n value: 'gitops-core',\n labelKey: 'vibecontrols.plugins.category.gitopsCore',\n labelFallback: 'GitOps Core',\n },\n {\n value: 'gitops-provider',\n labelKey: 'vibecontrols.plugins.category.gitopsProvider',\n labelFallback: 'GitOps Providers',\n },\n {\n value: 'security-core',\n labelKey: 'vibecontrols.plugins.category.securityCore',\n labelFallback: 'Security Core',\n },\n {\n value: 'security-provider',\n labelKey: 'vibecontrols.plugins.category.securityProvider',\n labelFallback: 'Security Providers',\n },\n { value: 'tool', labelKey: 'vibecontrols.plugins.category.tool', labelFallback: 'Tools' },\n];\n\nfunction derivePluginType(category: PluginMeta['category']): PluginType {\n if (category.endsWith('-core')) return 'Meta';\n if (category.endsWith('-provider')) return 'Provider';\n return 'Plugin';\n}\n\nfunction typeBadgeClasses(type: PluginType): string {\n switch (type) {\n case 'Meta':\n return 'bg-accent-purple-subtle text-text-primary border-accent-purple/30';\n case 'Provider':\n return 'bg-status-info-bg text-status-info-text border-status-info-border';\n default:\n return 'bg-bg-sunken text-text-secondary border-border-default';\n }\n}\n\ninterface CopyButtonProps {\n text: string;\n ariaLabel: string;\n}\n\nfunction CopyButton({ text, ariaLabel }: CopyButtonProps) {\n const [copied, setCopied] = useState(false);\n const handleCopy = useCallback(async () => {\n try {\n if (typeof navigator !== 'undefined' && navigator.clipboard) {\n await navigator.clipboard.writeText(text);\n }\n setCopied(true);\n window.setTimeout(() => setCopied(false), 1500);\n } catch {\n setCopied(false);\n }\n }, [text]);\n\n return (\n <button\n type=\"button\"\n onClick={handleCopy}\n aria-label={ariaLabel}\n className=\"inline-flex items-center justify-center size-7 rounded border border-border-default bg-bg-surface text-text-secondary hover:text-text-primary hover:bg-bg-elevated transition-colors flex-shrink-0\"\n >\n {copied ? <Check className=\"size-3.5\" /> : <Copy className=\"size-3.5\" />}\n </button>\n );\n}\n\ninterface PluginCardProps {\n plugin: PluginMeta;\n}\n\nfunction PluginCard({ plugin }: PluginCardProps) {\n const tr = useTr();\n const type = derivePluginType(plugin.category);\n const installCommand = `npm install ${plugin.packageName}`;\n const usageSnippet = `import plugin from '${plugin.packageName}';`;\n\n const [showUsage, setShowUsage] = useState(false);\n const [showCommands, setShowCommands] = useState(false);\n\n return (\n <Card className=\"flex flex-col h-full p-0 bg-bg-surface border-border-default\">\n <div className=\"flex flex-col gap-3 p-4\">\n <div className=\"flex items-start justify-between gap-3\">\n <div className=\"min-w-0 flex-1\">\n <h3 className=\"text-base font-semibold text-text-primary truncate\">\n {plugin.displayName}\n </h3>\n <p className=\"text-xs text-text-secondary font-mono truncate mt-1\">\n {plugin.packageName}\n </p>\n </div>\n <span\n className={`inline-flex items-center gap-1 px-2 py-0.5 text-[10px] uppercase tracking-wide font-semibold rounded-full border ${typeBadgeClasses(type)} flex-shrink-0`}\n >\n {type}\n </span>\n </div>\n\n <p className=\"text-sm text-text-secondary leading-snug\">{plugin.description}</p>\n\n <div className=\"mt-1 space-y-1.5\">\n <p className=\"text-[11px] font-medium uppercase tracking-wide text-text-secondary\">\n {tr('vibecontrols.plugins.install', 'Install')}\n </p>\n <div className=\"flex items-center gap-2 rounded-md border border-border-default bg-bg-sunken px-2.5 py-1.5\">\n <code className=\"flex-1 text-xs font-mono text-text-primary truncate\">\n {installCommand}\n </code>\n <CopyButton\n text={installCommand}\n ariaLabel={tr(\n 'vibecontrols.plugins.copyInstall',\n 'Copy install command for {{name}}',\n {\n name: plugin.displayName,\n }\n )}\n />\n </div>\n </div>\n\n <div className=\"space-y-2\">\n <button\n type=\"button\"\n onClick={() => setShowUsage((value) => !value)}\n className=\"w-full flex items-center justify-between gap-2 text-left text-xs font-medium text-text-secondary hover:text-text-primary transition-colors\"\n aria-expanded={showUsage}\n >\n <span>{tr('vibecontrols.plugins.usage', 'Usage')}</span>\n <ChevronDown\n className={`size-3.5 transition-transform ${showUsage ? 'rotate-180' : ''}`}\n />\n </button>\n {showUsage && (\n <div className=\"flex items-start gap-2 rounded-md border border-border-default bg-bg-sunken px-2.5 py-2\">\n <code className=\"flex-1 text-xs font-mono text-text-primary whitespace-pre-wrap break-all\">\n {usageSnippet}\n </code>\n <CopyButton\n text={usageSnippet}\n ariaLabel={tr('vibecontrols.plugins.copyUsage', 'Copy usage snippet for {{name}}', {\n name: plugin.displayName,\n })}\n />\n </div>\n )}\n </div>\n\n <div className=\"space-y-2\">\n <button\n type=\"button\"\n onClick={() => setShowCommands((value) => !value)}\n className=\"w-full flex items-center justify-between gap-2 text-left text-xs font-medium text-text-secondary hover:text-text-primary transition-colors\"\n aria-expanded={showCommands}\n >\n <span>{tr('vibecontrols.plugins.commands', 'Command reference')}</span>\n <ChevronDown\n className={`size-3.5 transition-transform ${showCommands ? 'rotate-180' : ''}`}\n />\n </button>\n {showCommands && (\n <div className=\"rounded-md border border-border-default bg-bg-sunken px-2.5 py-2\">\n <p className=\"text-xs text-text-secondary\">\n {tr(\n 'vibecontrols.plugins.commandsHint',\n 'See the agent admin sub-panel or the plugin README for the documented command list.'\n )}\n </p>\n </div>\n )}\n </div>\n </div>\n </Card>\n );\n}\n\nexport function PluginsCatalogPage() {\n const tr = useTr();\n const [search, setSearch] = useState('');\n const [activeCategory, setActiveCategory] = useState<string>('all');\n\n const pluginList = useMemo(() => {\n return Object.values(PLUGIN_CATALOG).sort((a, b) => a.displayName.localeCompare(b.displayName));\n }, []);\n\n const filteredPlugins = useMemo(() => {\n const term = search.trim().toLowerCase();\n return pluginList.filter((plugin) => {\n if (activeCategory !== 'all' && plugin.category !== activeCategory) return false;\n if (!term) return true;\n const haystack =\n `${plugin.displayName} ${plugin.description} ${plugin.packageName}`.toLowerCase();\n return haystack.includes(term);\n });\n }, [pluginList, search, activeCategory]);\n\n return (\n <PageContainer padded={false}>\n <MotionPage className=\"flex flex-col h-full\">\n {/* Sticky header with search + filter chips */}\n <div className=\"sticky top-0 z-10 border-b border-border-subtle bg-bg-surface px-6 lg:px-8 pt-5 pb-4 space-y-3\">\n <div className=\"flex items-start gap-3\">\n <div className=\"flex size-9 flex-shrink-0 items-center justify-center rounded-lg bg-[var(--color-accent-soft)]\">\n <Plug className=\"size-5 text-action-primary-bg\" />\n </div>\n <div className=\"min-w-0 flex-1\">\n <h1 className=\"text-2xl font-semibold tracking-tight text-text-primary\">\n {tr('vibecontrols.plugins.title', 'Plugins')}\n </h1>\n <p className=\"text-sm text-text-secondary mt-0.5\">\n {tr(\n 'vibecontrols.plugins.description',\n 'Browse and install plugins to extend your VibeControls agents.'\n )}\n </p>\n </div>\n </div>\n\n <div className=\"relative\">\n <Search className=\"absolute left-3 top-1/2 -translate-y-1/2 size-4 text-text-secondary\" />\n <input\n type=\"search\"\n value={search}\n onChange={(event) => setSearch(event.target.value)}\n placeholder={tr(\n 'vibecontrols.plugins.searchPlaceholder',\n 'Search by name, description, or package…'\n )}\n aria-label={tr('vibecontrols.plugins.searchLabel', 'Search plugins')}\n className=\"w-full pl-10 pr-10 py-2 text-sm bg-bg-sunken border border-border-default rounded-md text-text-primary placeholder:text-text-placeholder focus:outline-none focus:ring-2 focus:ring-action-primary-bg focus:border-transparent transition-all\"\n />\n {search && (\n <button\n type=\"button\"\n onClick={() => setSearch('')}\n aria-label={tr('vibecontrols.plugins.clearSearch', 'Clear search')}\n className=\"absolute right-3 top-1/2 -translate-y-1/2 p-0.5 rounded hover:bg-bg-elevated transition-colors\"\n >\n <X className=\"size-4 text-text-secondary\" />\n </button>\n )}\n </div>\n\n <div\n className=\"flex flex-wrap gap-2\"\n role=\"tablist\"\n aria-label={tr('vibecontrols.plugins.categories', 'Plugin categories')}\n >\n {CATEGORY_OPTIONS.map((option) => {\n const isActive = activeCategory === option.value;\n return (\n <button\n key={option.value}\n type=\"button\"\n role=\"tab\"\n aria-selected={isActive}\n onClick={() => setActiveCategory(option.value)}\n className={`px-3 py-1 text-xs rounded-full border transition-colors ${\n isActive\n ? 'bg-action-primary-bg text-action-primary-text border-action-primary-bg'\n : 'bg-bg-surface text-text-secondary border-border-default hover:bg-bg-sunken hover:text-text-primary'\n }`}\n >\n {tr(option.labelKey, option.labelFallback)}\n </button>\n );\n })}\n </div>\n </div>\n\n {/* Grid body */}\n <div className=\"flex-1 px-4 sm:px-6 py-4\">\n {filteredPlugins.length === 0 ? (\n <div className=\"flex flex-col items-center justify-center text-center py-16 text-text-secondary\">\n <Package className=\"size-12 mb-3 opacity-50\" />\n <p className=\"text-sm font-medium text-text-primary\">\n {tr('vibecontrols.plugins.empty.title', 'No matching plugins')}\n </p>\n <p className=\"text-xs mt-1\">\n {tr(\n 'vibecontrols.plugins.empty.description',\n 'Try a different search term or category.'\n )}\n </p>\n </div>\n ) : (\n <div className=\"grid gap-3 grid-cols-1 sm:grid-cols-2 xl:grid-cols-3\">\n {filteredPlugins.map((plugin) => (\n <PluginCard key={plugin.packageName} plugin={plugin} />\n ))}\n </div>\n )}\n </div>\n </MotionPage>\n </PageContainer>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1409,7 +1409,7 @@ function jt() {
|
|
|
1409
1409
|
/* @__PURE__ */ h("button", {
|
|
1410
1410
|
type: "button",
|
|
1411
1411
|
onClick: () => A("terminal"),
|
|
1412
|
-
className: `p-1.5 rounded ${k === "terminal" ? "bg-[var(--color-accent-soft)] text-
|
|
1412
|
+
className: `p-1.5 rounded ${k === "terminal" ? "bg-[var(--color-accent-soft)] text-text-primary" : "text-text-secondary hover:bg-bg-surface/60"}`,
|
|
1413
1413
|
title: b("sessions.terminalViewTitle", "Terminal view"),
|
|
1414
1414
|
"aria-label": b("sessions.terminalViewTitle", "Terminal view"),
|
|
1415
1415
|
children: /* @__PURE__ */ h(rt, { className: "size-4 text-text-secondary" })
|
|
@@ -1417,7 +1417,7 @@ function jt() {
|
|
|
1417
1417
|
/* @__PURE__ */ h("button", {
|
|
1418
1418
|
type: "button",
|
|
1419
1419
|
onClick: () => A("grid"),
|
|
1420
|
-
className: `p-1.5 rounded ${k === "grid" ? "bg-[var(--color-accent-soft)] text-
|
|
1420
|
+
className: `p-1.5 rounded ${k === "grid" ? "bg-[var(--color-accent-soft)] text-text-primary" : "text-text-secondary hover:bg-bg-surface/60"}`,
|
|
1421
1421
|
title: b("sessions.gridViewTitle", "Grid view"),
|
|
1422
1422
|
"aria-label": b("sessions.gridViewTitle", "Grid view"),
|
|
1423
1423
|
children: /* @__PURE__ */ h(Qe, { className: "size-4 text-text-secondary" })
|
|
@@ -1425,7 +1425,7 @@ function jt() {
|
|
|
1425
1425
|
/* @__PURE__ */ h("button", {
|
|
1426
1426
|
type: "button",
|
|
1427
1427
|
onClick: () => A("list"),
|
|
1428
|
-
className: `p-1.5 rounded ${k === "list" ? "bg-[var(--color-accent-soft)] text-
|
|
1428
|
+
className: `p-1.5 rounded ${k === "list" ? "bg-[var(--color-accent-soft)] text-text-primary" : "text-text-secondary hover:bg-bg-surface/60"}`,
|
|
1429
1429
|
title: b("sessions.listViewTitle", "List view"),
|
|
1430
1430
|
"aria-label": b("sessions.listViewTitle", "List view"),
|
|
1431
1431
|
children: /* @__PURE__ */ h(nt, { className: "size-4 text-text-secondary" })
|