@burdenoff/microfe-vibecontrols 2026.624.2 → 2026.624.3
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/VibeControlsRoutes.js +1 -1
- package/dist/VibeControlsRoutes.js.map +1 -1
- package/dist/components/agents/AgentStatusBadge.js +1 -1
- package/dist/components/agents/AgentStatusBadge.js.map +1 -1
- package/dist/components/agents/ConnectionLogPanel.js +1 -1
- package/dist/components/agents/ConnectionLogPanel.js.map +1 -1
- package/dist/components/agents/PluginUIPanel.js +1 -1
- package/dist/components/agents/PluginUIPanel.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/DocsFullScreenViewer.js +1 -1
- package/dist/components/docs/DocsFullScreenViewer.js.map +1 -1
- package/dist/components/notes/NoteEditor.js +1 -1
- package/dist/components/notes/NoteEditor.js.map +1 -1
- package/dist/components/sessions/TerminalPanel.js +3 -3
- package/dist/components/sessions/TerminalPanel.js.map +1 -1
- package/dist/components/shared/ActionMenu.js +1 -1
- package/dist/components/shared/ActionMenu.js.map +1 -1
- package/dist/components/shared/ErrorState.js +1 -1
- package/dist/components/shared/ErrorState.js.map +1 -1
- package/dist/components/vibecalendar/ScheduleTaskModal.js +3 -3
- package/dist/components/vibecalendar/ScheduleTaskModal.js.map +1 -1
- package/dist/components/vibecalendar/types.js +5 -5
- package/dist/components/vibecalendar/types.js.map +1 -1
- package/dist/pages/NoteDetailsPage.js +2 -2
- package/dist/pages/NoteDetailsPage.js.map +1 -1
- package/dist/pages/SessionDetailsPage.js +1 -1
- package/dist/pages/SessionDetailsPage.js.map +1 -1
- package/dist/pages/TargetsPage.js +3 -3
- package/dist/pages/TargetsPage.js.map +1 -1
- package/package.json +1 -1
|
@@ -16,7 +16,7 @@ var p = t.lazy(() => import("./pages/OverviewPage.js").then((e) => ({ default: e
|
|
|
16
16
|
function ae() {
|
|
17
17
|
return /* @__PURE__ */ l("div", {
|
|
18
18
|
className: "flex items-center justify-center h-full min-h-[200px]",
|
|
19
|
-
children: /* @__PURE__ */ l("div", { className: "animate-pulse bg-
|
|
19
|
+
children: /* @__PURE__ */ l("div", { className: "animate-pulse bg-bg-sunken rounded w-full h-full" })
|
|
20
20
|
});
|
|
21
21
|
}
|
|
22
22
|
function oe() {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"VibeControlsRoutes.js","names":[],"sources":["../src/VibeControlsRoutes.tsx"],"sourcesContent":["import React, { Suspense, useEffect } from 'react';\nimport { Navigate, Route, Routes, useLocation, useParams, useNavigate } from 'react-router-dom';\nimport { FeatureGate } from '@burdenoff/fe-libs/shared/feature-flags';\nimport { NotFoundPage } from '@burdenoff/fe-libs/shared/pages';\n\n/** Catch-all renderer: a friendly 404 inside the content area (was a broken\n * redirect to \"\" that left the page blank). \"Back to home\" returns to the\n * VibeControls overview via SPA navigation. */\nfunction VibeNotFound() {\n const navigate = useNavigate();\n return (\n <NotFoundPage homeLabel=\"Back to VibeControls\" onGoHome={() => navigate('/vibecontrols')} />\n );\n}\nimport { useTr } from './shared/hooks/useTr';\n\n// Lazy load - All pages (required for @/ alias resolution when consumed by host app)\nconst OverviewPage = React.lazy(() =>\n import('./pages/OverviewPage').then((m) => ({ default: m.OverviewPage }))\n);\n\n// Other pages\nconst AgentsLandingPage = React.lazy(() =>\n import('./pages/AgentsLandingPage').then((m) => ({ default: m.AgentsLandingPage }))\n);\nconst AgentDetailsPage = React.lazy(() =>\n import('./pages/AgentDetailsPage').then((m) => ({ default: m.AgentDetailsPage }))\n);\nconst TargetDetailsPage = React.lazy(() =>\n import('./pages/TargetDetailsPage').then((m) => ({ default: m.TargetDetailsPage }))\n);\nconst SessionsPage = React.lazy(() =>\n import('./pages/SessionsPage').then((m) => ({ default: m.SessionsPage }))\n);\nconst SessionDetailsPage = React.lazy(() =>\n import('./pages/SessionDetailsPage').then((m) => ({ default: m.SessionDetailsPage }))\n);\nconst VibesPage = React.lazy(() =>\n import('./pages/VibesPage').then((m) => ({ default: m.VibesPage }))\n);\nconst VibeDetailsPage = React.lazy(() =>\n import('./pages/VibeDetailsPage').then((m) => ({ default: m.VibeDetailsPage }))\n);\nconst NoteDetailsPage = React.lazy(() =>\n import('./pages/NoteDetailsPage').then((m) => ({ default: m.NoteDetailsPage }))\n);\nconst TunnelsLandingPage = React.lazy(() =>\n import('./pages/TunnelsLandingPage').then((m) => ({ default: m.TunnelsLandingPage }))\n);\nconst TunnelDetailsPage = React.lazy(() =>\n import('./pages/tunnels/TunnelDetailsPage').then((m) => ({ default: m.TunnelDetailsPage }))\n);\nconst TunnelCreateWizard = React.lazy(() =>\n import('./components/tunnels/TunnelCreateWizard').then((m) => ({ default: m.TunnelCreateWizard }))\n);\nconst SettingsPage = React.lazy(() =>\n import('./pages/SettingsPage').then((m) => ({ default: m.SettingsPage }))\n);\nconst HealthPage = React.lazy(() =>\n import('./pages/HealthPage').then((m) => ({ default: m.HealthPage }))\n);\nconst GettingStartedPage = React.lazy(() =>\n import('./pages/GettingStartedPage').then((m) => ({ default: m.GettingStartedPage }))\n);\nconst SharedSessionPage = React.lazy(() =>\n import('./pages/SharedSessionPage').then((m) => ({ default: m.SharedSessionPage }))\n);\nconst VibeDeckPage = React.lazy(() =>\n import('./pages/VibeDeckPage').then((m) => ({ default: m.VibeDeckPage }))\n);\nconst SharedVibeDeckPage = React.lazy(() =>\n import('./pages/SharedVibeDeckPage').then((m) => ({ default: m.SharedVibeDeckPage }))\n);\nconst VibeCalendarPage = React.lazy(() =>\n import('./pages/VibeCalendarPage').then((m) => ({ default: m.VibeCalendarPage }))\n);\nconst VibeTaskDetailPage = React.lazy(() =>\n import('./pages/VibeTaskDetailPage').then((m) => ({ default: m.VibeTaskDetailPage }))\n);\nconst ScheduledJobDetailPage = React.lazy(() =>\n import('./pages/ScheduledJobDetailPage').then((m) => ({ default: m.ScheduledJobDetailPage }))\n);\nconst ActionsPage = React.lazy(() =>\n import('./pages/ActionsPage').then((m) => ({ default: m.ActionsPage }))\n);\nconst VibeAuditPage = React.lazy(() =>\n import('./pages/VibeAuditPage').then((m) => ({ default: m.VibeAuditPage }))\n);\nconst AnalyticsPage = React.lazy(() =>\n import('./pages/AnalyticsPage').then((m) => ({ default: m.AnalyticsPage }))\n);\nconst VibeDocsPage = React.lazy(() =>\n import('./pages/VibeDocsPage').then((m) => ({ default: m.VibeDocsPage }))\n);\nconst LogsPage = React.lazy(() =>\n import('./pages/LogsPage').then((m) => ({ default: m.LogsPage }))\n);\n\n// Catalog, Components, Templates pages\nconst CatalogPage = React.lazy(() =>\n import('./pages/CatalogPage').then((m) => ({ default: m.CatalogPage }))\n);\nconst CatalogDetailPage = React.lazy(() =>\n import('./pages/CatalogDetailPage').then((m) => ({ default: m.CatalogDetailPage }))\n);\nconst CatalogDocsPage = React.lazy(() =>\n import('./pages/CatalogDocsPage').then((m) => ({ default: m.CatalogDocsPage }))\n);\nconst ComponentsPage = React.lazy(() =>\n import('./pages/ComponentsPage').then((m) => ({ default: m.ComponentsPage }))\n);\nconst EnvTopologyPage = React.lazy(() =>\n import('./pages/EnvTopologyPage').then((m) => ({ default: m.EnvTopologyPage }))\n);\nconst WebhooksPage = React.lazy(() =>\n import('./pages/WebhooksPage').then((m) => ({ default: m.WebhooksPage }))\n);\nconst ComponentDetailPage = React.lazy(() =>\n import('./pages/ComponentDetailPage').then((m) => ({ default: m.ComponentDetailPage }))\n);\nconst ComponentDocsPage = React.lazy(() =>\n import('./pages/ComponentDocsPage').then((m) => ({ default: m.ComponentDocsPage }))\n);\nconst TemplatesPage = React.lazy(() =>\n import('./pages/TemplatesPage').then((m) => ({ default: m.TemplatesPage }))\n);\nconst TemplateDetailPage = React.lazy(() =>\n import('./pages/TemplateDetailPage').then((m) => ({ default: m.TemplateDetailPage }))\n);\nconst PluginsCatalogPage = React.lazy(() =>\n import('./pages/PluginsCatalogPage').then((m) => ({ default: m.PluginsCatalogPage }))\n);\n\n// AI pages\nconst AIPage = React.lazy(() => import('./pages/ai/AIPage').then((m) => ({ default: m.AIPage })));\nconst AISessionDetailPage = React.lazy(() =>\n import('./pages/ai/AISessionDetailPage').then((m) => ({ default: m.AISessionDetailPage }))\n);\nconst AgentManagerPage = React.lazy(() =>\n import('./pages/ai/AgentManagerPage').then((m) => ({ default: m.AgentManagerPage }))\n);\nconst AIBookmarksPage = React.lazy(() =>\n import('./pages/ai/AIBookmarksPage').then((m) => ({ default: m.AIBookmarksPage }))\n);\nconst AIBuilderPage = React.lazy(() =>\n import('./pages/ai/AIBuilderPage').then((m) => ({ default: m.AIBuilderPage }))\n);\n\n// Loading fallback component\nfunction PageLoadingFallback() {\n return (\n <div className=\"flex items-center justify-center h-full min-h-[200px]\">\n <div className=\"animate-pulse bg-muted rounded w-full h-full\" />\n </div>\n );\n}\n\n/**\n * Internal router for the VibeControls microfrontend.\n * This component handles routing within the microfrontend using React Router v7.\n *\n * Routes:\n * - / → Overview page (dashboard)\n * - /agents → Agents management page\n * - /agents/:id → Agent details page\n * - /sessions → Sessions management page\n * - /sessions/:id → Session details page\n * - /vibes → Vibes (projects) management page\n * - /vibes/:id → Vibe details page (includes Notes tab)\n * - /settings → Settings page\n * - /webhooks → Legacy redirect to Developer Portal webhooks\n * - /health → Health monitoring page\n * - /* → Catch-all redirects to overview\n */\nfunction ScrollToTop() {\n const { pathname } = useLocation();\n useEffect(() => {\n // The scrollable container is the <main> element with overflow-auto\n // inside AppShellLayout, not the window itself.\n const main = document.querySelector('main');\n if (main) main.scrollTop = 0;\n }, [pathname]);\n return null;\n}\n\n/**\n * Legacy redirect: /ai-tools/<tab> → /ai-tools/ai/<tab>.\n *\n * Before the Plan / AI mode toggle the page was reachable directly at\n * /ai-tools/<tab>. New shape is /ai-tools/<mode>/<tab>. We keep the\n * legacy links working by mapping the captured `:tab` param: if it\n * resolves to a known AI sub-tab, redirect there; if it's literally\n * \"plan\", route to the Plan page; otherwise fall back to AI overview.\n */\nconst VALID_AI_TABS = new Set<string>([\n 'overview',\n 'templates',\n 'contexts',\n 'prompts',\n 'sessions',\n 'tasks',\n 'stats',\n 'playground',\n 'config',\n]);\n\nfunction LegacyAIToolsRedirect() {\n const { tab } = useParams<{ tab?: string }>();\n if (tab === 'plan') {\n return <Navigate to=\"/vibecontrols/ai-tools/plan\" replace />;\n }\n if (tab && VALID_AI_TABS.has(tab)) {\n return <Navigate to={`/vibecontrols/ai-tools/ai/${tab}`} replace />;\n }\n return <Navigate to=\"/vibecontrols/ai-tools/ai/overview\" replace />;\n}\n\n/**\n * Fallback shown when a feature flag is OFF.\n */\nfunction FeatureDisabledFallback() {\n const tr = useTr();\n return (\n <div className=\"flex items-center justify-center h-full min-h-[200px] text-text-secondary text-sm\">\n {tr(\n 'vibecontrols.pages.featureDisabled',\n 'This feature is currently disabled. You can enable it from Feature Previews.'\n )}\n </div>\n );\n}\n\n/**\n * Helper: wraps a page element in a FeatureGate.\n * defaultValue={true} means the feature shows by default (before flags are seeded).\n * Only hides when a flag is explicitly set to false.\n */\nfunction gated(flag: string, element: React.ReactNode, defaultValue = true): React.ReactElement {\n return (\n <FeatureGate flag={flag} defaultValue={defaultValue} fallback={<FeatureDisabledFallback />}>\n {element}\n </FeatureGate>\n );\n}\n\nexport function VibeControlsRoutes() {\n return (\n <Suspense fallback={<PageLoadingFallback />}>\n <ScrollToTop />\n <Routes>\n {/* Default route - overview page */}\n <Route index element={gated('vibecontrols.overview', <OverviewPage />)} />\n {/* Legacy /dashboard alias → overview */}\n <Route path=\"dashboard\" element={<Navigate to=\"/vibecontrols\" replace />} />\n\n {/* Core pages */}\n {/* Plugins catalog — searchable grid of every published plugin. */}\n <Route path=\"plugins\" element={gated('vibecontrols.vibes', <PluginsCatalogPage />)} />\n\n <Route path=\"agents\" element={<AgentsLandingPage />} />\n <Route path=\"agents/new\" element={<Navigate to=\"../agents?create=true\" replace />} />\n <Route path=\"agents/:id\" element={<AgentDetailsPage />} />\n <Route path=\"sessions\" element={<SessionsPage />} />\n <Route path=\"vibes\" element={gated('vibecontrols.vibes', <VibesPage />)} />\n <Route path=\"vibes/new\" element={<Navigate to=\"../vibes?create=true\" replace />} />\n <Route path=\"vibes/:id\" element={gated('vibecontrols.vibes', <VibeDetailsPage />)} />\n <Route path=\"vibes/:id/docs/*\" element={gated('vibecontrols.vibes', <VibeDocsPage />)} />\n <Route path=\"notes/:id\" element={gated('vibecontrols.vibes', <NoteDetailsPage />)} />\n <Route path=\"sessions/:id\" element={<SessionDetailsPage />} />\n {/* Agent Manager — multi-session tabbed UI (replaces old AI page) */}\n <Route path=\"ai\" element={gated('vibecontrols.ai', <AgentManagerPage />)} />\n {/* Saved messages — listed BEFORE ai/:tab so \"bookmarks\" is not consumed as a tab. */}\n <Route path=\"ai/bookmarks\" element={gated('vibecontrols.ai', <AIBookmarksPage />)} />\n {/* AI Builder — describe → scaffold → test in sandbox → publish to catalog. */}\n <Route path=\"ai-builder\" element={gated('vibecontrols.ai', <AIBuilderPage />)} />\n <Route path=\"ai/:tab\" element={gated('vibecontrols.ai', <AgentManagerPage />)} />\n <Route\n path=\"ai/sessions/:sessionId\"\n element={gated('vibecontrols.ai', <AgentManagerPage />)}\n />\n {/* AI Tools — Plan / AI modes:\n /ai-tools → /ai-tools/ai/overview (redirect)\n /ai-tools/plan → Plan mode (plannotator iframe)\n /ai-tools/ai → /ai-tools/ai/overview (redirect)\n /ai-tools/ai/:tab → AI mode with active sub-tab\n Legacy /ai-tools/<tab> URLs from before the Plan mode rollout\n redirect into the new shape via LegacyAIToolsRedirect. */}\n <Route path=\"ai-tools\" element={<Navigate to=\"ai/overview\" replace />} />\n <Route path=\"ai-tools/plan\" element={gated('vibecontrols.ai', <AIPage />)} />\n <Route path=\"ai-tools/ai\" element={<Navigate to=\"overview\" replace />} />\n <Route path=\"ai-tools/ai/:tab\" element={gated('vibecontrols.ai', <AIPage />)} />\n <Route path=\"ai-tools/:tab\" element={<LegacyAIToolsRedirect />} />\n <Route\n path=\"ai-tools/sessions/:sessionId\"\n element={gated('vibecontrols.ai', <AISessionDetailPage />)}\n />\n\n {/* Targets list lives in the Targets & Agents landing; per-target\n detail is its own page (mirrors agents/:id). */}\n <Route path=\"targets\" element={<Navigate to=\"../agents?tab=targets\" replace />} />\n <Route path=\"targets/:id\" element={<TargetDetailsPage />} />\n {/* Agent Graph is also a tab inside the Targets & Agents landing. */}\n <Route path=\"agent-graph\" element={<Navigate to=\"../agents?tab=graph\" replace />} />\n\n {/* Catalog */}\n <Route path=\"catalogs\" element={gated('vibecontrols.catalogs', <CatalogPage />)} />\n <Route\n path=\"catalogs/:id\"\n element={gated('vibecontrols.catalogs', <CatalogDetailPage />)}\n />\n <Route\n path=\"catalogs/:id/docs/*\"\n element={gated('vibecontrols.catalogs', <CatalogDocsPage />)}\n />\n <Route path=\"components\" element={gated('vibecontrols.components', <ComponentsPage />)} />\n <Route\n path=\"environments\"\n element={gated('vibecontrols.components', <EnvTopologyPage />)}\n />\n <Route\n path=\"components/:id\"\n element={gated('vibecontrols.components', <ComponentDetailPage />)}\n />\n <Route\n path=\"components/:id/docs/*\"\n element={gated('vibecontrols.components', <ComponentDocsPage />)}\n />\n <Route path=\"templates\" element={gated('vibecontrols.templates', <TemplatesPage />)} />\n <Route\n path=\"templates/:id\"\n element={gated('vibecontrols.templates', <TemplateDetailPage />)}\n />\n\n {/* Automation */}\n <Route path=\"actions\" element={gated('vibecontrols.actions', <ActionsPage />)} />\n <Route path=\"vibedeck\" element={gated('vibecontrols.vibedeck', <VibeDeckPage />)} />\n <Route path=\"calendar\" element={gated('vibecontrols.calendar', <VibeCalendarPage />)} />\n <Route\n path=\"calendar/:viewType\"\n element={gated('vibecontrols.calendar', <VibeCalendarPage />)}\n />\n <Route\n path=\"calendar/task/:taskId\"\n element={gated('vibecontrols.calendar', <VibeTaskDetailPage />)}\n />\n {/* ScheduledJob (wspace-scheduler-svc) — separate id space\n from VibeCalendarTask above, so it needs its own route. */}\n <Route\n path=\"calendar/scheduled-job/:jobId\"\n element={gated('vibecontrols.calendar', <ScheduledJobDetailPage />)}\n />\n <Route path=\"webhooks\" element={gated('vibecontrols.webhooks', <WebhooksPage />)} />\n <Route path=\"webhooks/new\" element={gated('vibecontrols.webhooks', <WebhooksPage />)} />\n <Route path=\"webhooks/:id\" element={gated('vibecontrols.webhooks', <WebhooksPage />)} />\n {/* GitOps consolidated into Vibe detail tab — old /gitops routes redirect to vibes list. */}\n <Route path=\"gitops\" element={<Navigate to=\"../vibes\" replace />} />\n <Route path=\"gitops/:tab\" element={<Navigate to=\"../vibes\" replace />} />\n\n {/* Infrastructure */}\n <Route path=\"tunnels\" element={gated('vibecontrols.tunnels', <TunnelsLandingPage />)} />\n <Route path=\"tunnels/new\" element={gated('vibecontrols.tunnels', <TunnelCreateWizard />)} />\n <Route\n path=\"tunnels/:tunnelId\"\n element={gated('vibecontrols.tunnels', <TunnelDetailsPage />)}\n />\n\n {/* Insights */}\n <Route path=\"health\" element={gated('vibecontrols.health', <HealthPage />)} />\n <Route path=\"analytics\" element={gated('vibecontrols.analytics', <AnalyticsPage />)} />\n <Route path=\"audit\" element={gated('vibecontrols.audit-log', <VibeAuditPage />, false)} />\n <Route path=\"logs\" element={<LogsPage />} />\n\n {/* Settings */}\n <Route path=\"settings\" element={gated('vibecontrols.settings', <SettingsPage />)} />\n\n {/* Setup */}\n {/* Not gated — onboarding flow must always be reachable */}\n <Route path=\"getting-started\" element={<GettingStartedPage />} />\n\n {/* Shared public routes (no gating — accessible via public links) */}\n <Route path=\"share/:token\" element={<SharedSessionPage />} />\n <Route path=\"deck/:token\" element={<SharedVibeDeckPage />} />\n\n {/* Legacy redirects */}\n <Route path=\"access\" element={<Navigate to=\"../agents?tab=targets\" replace />} />\n <Route path=\"ssh-targets\" element={<Navigate to=\"../agents?tab=targets\" replace />} />\n <Route path=\"ssh-terminals\" element={<Navigate to=\"../agents?tab=targets\" replace />} />\n\n {/* Catch-all route - redirect to overview */}\n <Route path=\"*\" element={<VibeNotFound />} />\n </Routes>\n </Suspense>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"VibeControlsRoutes.js","names":[],"sources":["../src/VibeControlsRoutes.tsx"],"sourcesContent":["import React, { Suspense, useEffect } from 'react';\nimport { Navigate, Route, Routes, useLocation, useParams, useNavigate } from 'react-router-dom';\nimport { FeatureGate } from '@burdenoff/fe-libs/shared/feature-flags';\nimport { NotFoundPage } from '@burdenoff/fe-libs/shared/pages';\n\n/** Catch-all renderer: a friendly 404 inside the content area (was a broken\n * redirect to \"\" that left the page blank). \"Back to home\" returns to the\n * VibeControls overview via SPA navigation. */\nfunction VibeNotFound() {\n const navigate = useNavigate();\n return (\n <NotFoundPage homeLabel=\"Back to VibeControls\" onGoHome={() => navigate('/vibecontrols')} />\n );\n}\nimport { useTr } from './shared/hooks/useTr';\n\n// Lazy load - All pages (required for @/ alias resolution when consumed by host app)\nconst OverviewPage = React.lazy(() =>\n import('./pages/OverviewPage').then((m) => ({ default: m.OverviewPage }))\n);\n\n// Other pages\nconst AgentsLandingPage = React.lazy(() =>\n import('./pages/AgentsLandingPage').then((m) => ({ default: m.AgentsLandingPage }))\n);\nconst AgentDetailsPage = React.lazy(() =>\n import('./pages/AgentDetailsPage').then((m) => ({ default: m.AgentDetailsPage }))\n);\nconst TargetDetailsPage = React.lazy(() =>\n import('./pages/TargetDetailsPage').then((m) => ({ default: m.TargetDetailsPage }))\n);\nconst SessionsPage = React.lazy(() =>\n import('./pages/SessionsPage').then((m) => ({ default: m.SessionsPage }))\n);\nconst SessionDetailsPage = React.lazy(() =>\n import('./pages/SessionDetailsPage').then((m) => ({ default: m.SessionDetailsPage }))\n);\nconst VibesPage = React.lazy(() =>\n import('./pages/VibesPage').then((m) => ({ default: m.VibesPage }))\n);\nconst VibeDetailsPage = React.lazy(() =>\n import('./pages/VibeDetailsPage').then((m) => ({ default: m.VibeDetailsPage }))\n);\nconst NoteDetailsPage = React.lazy(() =>\n import('./pages/NoteDetailsPage').then((m) => ({ default: m.NoteDetailsPage }))\n);\nconst TunnelsLandingPage = React.lazy(() =>\n import('./pages/TunnelsLandingPage').then((m) => ({ default: m.TunnelsLandingPage }))\n);\nconst TunnelDetailsPage = React.lazy(() =>\n import('./pages/tunnels/TunnelDetailsPage').then((m) => ({ default: m.TunnelDetailsPage }))\n);\nconst TunnelCreateWizard = React.lazy(() =>\n import('./components/tunnels/TunnelCreateWizard').then((m) => ({ default: m.TunnelCreateWizard }))\n);\nconst SettingsPage = React.lazy(() =>\n import('./pages/SettingsPage').then((m) => ({ default: m.SettingsPage }))\n);\nconst HealthPage = React.lazy(() =>\n import('./pages/HealthPage').then((m) => ({ default: m.HealthPage }))\n);\nconst GettingStartedPage = React.lazy(() =>\n import('./pages/GettingStartedPage').then((m) => ({ default: m.GettingStartedPage }))\n);\nconst SharedSessionPage = React.lazy(() =>\n import('./pages/SharedSessionPage').then((m) => ({ default: m.SharedSessionPage }))\n);\nconst VibeDeckPage = React.lazy(() =>\n import('./pages/VibeDeckPage').then((m) => ({ default: m.VibeDeckPage }))\n);\nconst SharedVibeDeckPage = React.lazy(() =>\n import('./pages/SharedVibeDeckPage').then((m) => ({ default: m.SharedVibeDeckPage }))\n);\nconst VibeCalendarPage = React.lazy(() =>\n import('./pages/VibeCalendarPage').then((m) => ({ default: m.VibeCalendarPage }))\n);\nconst VibeTaskDetailPage = React.lazy(() =>\n import('./pages/VibeTaskDetailPage').then((m) => ({ default: m.VibeTaskDetailPage }))\n);\nconst ScheduledJobDetailPage = React.lazy(() =>\n import('./pages/ScheduledJobDetailPage').then((m) => ({ default: m.ScheduledJobDetailPage }))\n);\nconst ActionsPage = React.lazy(() =>\n import('./pages/ActionsPage').then((m) => ({ default: m.ActionsPage }))\n);\nconst VibeAuditPage = React.lazy(() =>\n import('./pages/VibeAuditPage').then((m) => ({ default: m.VibeAuditPage }))\n);\nconst AnalyticsPage = React.lazy(() =>\n import('./pages/AnalyticsPage').then((m) => ({ default: m.AnalyticsPage }))\n);\nconst VibeDocsPage = React.lazy(() =>\n import('./pages/VibeDocsPage').then((m) => ({ default: m.VibeDocsPage }))\n);\nconst LogsPage = React.lazy(() =>\n import('./pages/LogsPage').then((m) => ({ default: m.LogsPage }))\n);\n\n// Catalog, Components, Templates pages\nconst CatalogPage = React.lazy(() =>\n import('./pages/CatalogPage').then((m) => ({ default: m.CatalogPage }))\n);\nconst CatalogDetailPage = React.lazy(() =>\n import('./pages/CatalogDetailPage').then((m) => ({ default: m.CatalogDetailPage }))\n);\nconst CatalogDocsPage = React.lazy(() =>\n import('./pages/CatalogDocsPage').then((m) => ({ default: m.CatalogDocsPage }))\n);\nconst ComponentsPage = React.lazy(() =>\n import('./pages/ComponentsPage').then((m) => ({ default: m.ComponentsPage }))\n);\nconst EnvTopologyPage = React.lazy(() =>\n import('./pages/EnvTopologyPage').then((m) => ({ default: m.EnvTopologyPage }))\n);\nconst WebhooksPage = React.lazy(() =>\n import('./pages/WebhooksPage').then((m) => ({ default: m.WebhooksPage }))\n);\nconst ComponentDetailPage = React.lazy(() =>\n import('./pages/ComponentDetailPage').then((m) => ({ default: m.ComponentDetailPage }))\n);\nconst ComponentDocsPage = React.lazy(() =>\n import('./pages/ComponentDocsPage').then((m) => ({ default: m.ComponentDocsPage }))\n);\nconst TemplatesPage = React.lazy(() =>\n import('./pages/TemplatesPage').then((m) => ({ default: m.TemplatesPage }))\n);\nconst TemplateDetailPage = React.lazy(() =>\n import('./pages/TemplateDetailPage').then((m) => ({ default: m.TemplateDetailPage }))\n);\nconst PluginsCatalogPage = React.lazy(() =>\n import('./pages/PluginsCatalogPage').then((m) => ({ default: m.PluginsCatalogPage }))\n);\n\n// AI pages\nconst AIPage = React.lazy(() => import('./pages/ai/AIPage').then((m) => ({ default: m.AIPage })));\nconst AISessionDetailPage = React.lazy(() =>\n import('./pages/ai/AISessionDetailPage').then((m) => ({ default: m.AISessionDetailPage }))\n);\nconst AgentManagerPage = React.lazy(() =>\n import('./pages/ai/AgentManagerPage').then((m) => ({ default: m.AgentManagerPage }))\n);\nconst AIBookmarksPage = React.lazy(() =>\n import('./pages/ai/AIBookmarksPage').then((m) => ({ default: m.AIBookmarksPage }))\n);\nconst AIBuilderPage = React.lazy(() =>\n import('./pages/ai/AIBuilderPage').then((m) => ({ default: m.AIBuilderPage }))\n);\n\n// Loading fallback component\nfunction PageLoadingFallback() {\n return (\n <div className=\"flex items-center justify-center h-full min-h-[200px]\">\n <div className=\"animate-pulse bg-bg-sunken rounded w-full h-full\" />\n </div>\n );\n}\n\n/**\n * Internal router for the VibeControls microfrontend.\n * This component handles routing within the microfrontend using React Router v7.\n *\n * Routes:\n * - / → Overview page (dashboard)\n * - /agents → Agents management page\n * - /agents/:id → Agent details page\n * - /sessions → Sessions management page\n * - /sessions/:id → Session details page\n * - /vibes → Vibes (projects) management page\n * - /vibes/:id → Vibe details page (includes Notes tab)\n * - /settings → Settings page\n * - /webhooks → Legacy redirect to Developer Portal webhooks\n * - /health → Health monitoring page\n * - /* → Catch-all redirects to overview\n */\nfunction ScrollToTop() {\n const { pathname } = useLocation();\n useEffect(() => {\n // The scrollable container is the <main> element with overflow-auto\n // inside AppShellLayout, not the window itself.\n const main = document.querySelector('main');\n if (main) main.scrollTop = 0;\n }, [pathname]);\n return null;\n}\n\n/**\n * Legacy redirect: /ai-tools/<tab> → /ai-tools/ai/<tab>.\n *\n * Before the Plan / AI mode toggle the page was reachable directly at\n * /ai-tools/<tab>. New shape is /ai-tools/<mode>/<tab>. We keep the\n * legacy links working by mapping the captured `:tab` param: if it\n * resolves to a known AI sub-tab, redirect there; if it's literally\n * \"plan\", route to the Plan page; otherwise fall back to AI overview.\n */\nconst VALID_AI_TABS = new Set<string>([\n 'overview',\n 'templates',\n 'contexts',\n 'prompts',\n 'sessions',\n 'tasks',\n 'stats',\n 'playground',\n 'config',\n]);\n\nfunction LegacyAIToolsRedirect() {\n const { tab } = useParams<{ tab?: string }>();\n if (tab === 'plan') {\n return <Navigate to=\"/vibecontrols/ai-tools/plan\" replace />;\n }\n if (tab && VALID_AI_TABS.has(tab)) {\n return <Navigate to={`/vibecontrols/ai-tools/ai/${tab}`} replace />;\n }\n return <Navigate to=\"/vibecontrols/ai-tools/ai/overview\" replace />;\n}\n\n/**\n * Fallback shown when a feature flag is OFF.\n */\nfunction FeatureDisabledFallback() {\n const tr = useTr();\n return (\n <div className=\"flex items-center justify-center h-full min-h-[200px] text-text-secondary text-sm\">\n {tr(\n 'vibecontrols.pages.featureDisabled',\n 'This feature is currently disabled. You can enable it from Feature Previews.'\n )}\n </div>\n );\n}\n\n/**\n * Helper: wraps a page element in a FeatureGate.\n * defaultValue={true} means the feature shows by default (before flags are seeded).\n * Only hides when a flag is explicitly set to false.\n */\nfunction gated(flag: string, element: React.ReactNode, defaultValue = true): React.ReactElement {\n return (\n <FeatureGate flag={flag} defaultValue={defaultValue} fallback={<FeatureDisabledFallback />}>\n {element}\n </FeatureGate>\n );\n}\n\nexport function VibeControlsRoutes() {\n return (\n <Suspense fallback={<PageLoadingFallback />}>\n <ScrollToTop />\n <Routes>\n {/* Default route - overview page */}\n <Route index element={gated('vibecontrols.overview', <OverviewPage />)} />\n {/* Legacy /dashboard alias → overview */}\n <Route path=\"dashboard\" element={<Navigate to=\"/vibecontrols\" replace />} />\n\n {/* Core pages */}\n {/* Plugins catalog — searchable grid of every published plugin. */}\n <Route path=\"plugins\" element={gated('vibecontrols.vibes', <PluginsCatalogPage />)} />\n\n <Route path=\"agents\" element={<AgentsLandingPage />} />\n <Route path=\"agents/new\" element={<Navigate to=\"../agents?create=true\" replace />} />\n <Route path=\"agents/:id\" element={<AgentDetailsPage />} />\n <Route path=\"sessions\" element={<SessionsPage />} />\n <Route path=\"vibes\" element={gated('vibecontrols.vibes', <VibesPage />)} />\n <Route path=\"vibes/new\" element={<Navigate to=\"../vibes?create=true\" replace />} />\n <Route path=\"vibes/:id\" element={gated('vibecontrols.vibes', <VibeDetailsPage />)} />\n <Route path=\"vibes/:id/docs/*\" element={gated('vibecontrols.vibes', <VibeDocsPage />)} />\n <Route path=\"notes/:id\" element={gated('vibecontrols.vibes', <NoteDetailsPage />)} />\n <Route path=\"sessions/:id\" element={<SessionDetailsPage />} />\n {/* Agent Manager — multi-session tabbed UI (replaces old AI page) */}\n <Route path=\"ai\" element={gated('vibecontrols.ai', <AgentManagerPage />)} />\n {/* Saved messages — listed BEFORE ai/:tab so \"bookmarks\" is not consumed as a tab. */}\n <Route path=\"ai/bookmarks\" element={gated('vibecontrols.ai', <AIBookmarksPage />)} />\n {/* AI Builder — describe → scaffold → test in sandbox → publish to catalog. */}\n <Route path=\"ai-builder\" element={gated('vibecontrols.ai', <AIBuilderPage />)} />\n <Route path=\"ai/:tab\" element={gated('vibecontrols.ai', <AgentManagerPage />)} />\n <Route\n path=\"ai/sessions/:sessionId\"\n element={gated('vibecontrols.ai', <AgentManagerPage />)}\n />\n {/* AI Tools — Plan / AI modes:\n /ai-tools → /ai-tools/ai/overview (redirect)\n /ai-tools/plan → Plan mode (plannotator iframe)\n /ai-tools/ai → /ai-tools/ai/overview (redirect)\n /ai-tools/ai/:tab → AI mode with active sub-tab\n Legacy /ai-tools/<tab> URLs from before the Plan mode rollout\n redirect into the new shape via LegacyAIToolsRedirect. */}\n <Route path=\"ai-tools\" element={<Navigate to=\"ai/overview\" replace />} />\n <Route path=\"ai-tools/plan\" element={gated('vibecontrols.ai', <AIPage />)} />\n <Route path=\"ai-tools/ai\" element={<Navigate to=\"overview\" replace />} />\n <Route path=\"ai-tools/ai/:tab\" element={gated('vibecontrols.ai', <AIPage />)} />\n <Route path=\"ai-tools/:tab\" element={<LegacyAIToolsRedirect />} />\n <Route\n path=\"ai-tools/sessions/:sessionId\"\n element={gated('vibecontrols.ai', <AISessionDetailPage />)}\n />\n\n {/* Targets list lives in the Targets & Agents landing; per-target\n detail is its own page (mirrors agents/:id). */}\n <Route path=\"targets\" element={<Navigate to=\"../agents?tab=targets\" replace />} />\n <Route path=\"targets/:id\" element={<TargetDetailsPage />} />\n {/* Agent Graph is also a tab inside the Targets & Agents landing. */}\n <Route path=\"agent-graph\" element={<Navigate to=\"../agents?tab=graph\" replace />} />\n\n {/* Catalog */}\n <Route path=\"catalogs\" element={gated('vibecontrols.catalogs', <CatalogPage />)} />\n <Route\n path=\"catalogs/:id\"\n element={gated('vibecontrols.catalogs', <CatalogDetailPage />)}\n />\n <Route\n path=\"catalogs/:id/docs/*\"\n element={gated('vibecontrols.catalogs', <CatalogDocsPage />)}\n />\n <Route path=\"components\" element={gated('vibecontrols.components', <ComponentsPage />)} />\n <Route\n path=\"environments\"\n element={gated('vibecontrols.components', <EnvTopologyPage />)}\n />\n <Route\n path=\"components/:id\"\n element={gated('vibecontrols.components', <ComponentDetailPage />)}\n />\n <Route\n path=\"components/:id/docs/*\"\n element={gated('vibecontrols.components', <ComponentDocsPage />)}\n />\n <Route path=\"templates\" element={gated('vibecontrols.templates', <TemplatesPage />)} />\n <Route\n path=\"templates/:id\"\n element={gated('vibecontrols.templates', <TemplateDetailPage />)}\n />\n\n {/* Automation */}\n <Route path=\"actions\" element={gated('vibecontrols.actions', <ActionsPage />)} />\n <Route path=\"vibedeck\" element={gated('vibecontrols.vibedeck', <VibeDeckPage />)} />\n <Route path=\"calendar\" element={gated('vibecontrols.calendar', <VibeCalendarPage />)} />\n <Route\n path=\"calendar/:viewType\"\n element={gated('vibecontrols.calendar', <VibeCalendarPage />)}\n />\n <Route\n path=\"calendar/task/:taskId\"\n element={gated('vibecontrols.calendar', <VibeTaskDetailPage />)}\n />\n {/* ScheduledJob (wspace-scheduler-svc) — separate id space\n from VibeCalendarTask above, so it needs its own route. */}\n <Route\n path=\"calendar/scheduled-job/:jobId\"\n element={gated('vibecontrols.calendar', <ScheduledJobDetailPage />)}\n />\n <Route path=\"webhooks\" element={gated('vibecontrols.webhooks', <WebhooksPage />)} />\n <Route path=\"webhooks/new\" element={gated('vibecontrols.webhooks', <WebhooksPage />)} />\n <Route path=\"webhooks/:id\" element={gated('vibecontrols.webhooks', <WebhooksPage />)} />\n {/* GitOps consolidated into Vibe detail tab — old /gitops routes redirect to vibes list. */}\n <Route path=\"gitops\" element={<Navigate to=\"../vibes\" replace />} />\n <Route path=\"gitops/:tab\" element={<Navigate to=\"../vibes\" replace />} />\n\n {/* Infrastructure */}\n <Route path=\"tunnels\" element={gated('vibecontrols.tunnels', <TunnelsLandingPage />)} />\n <Route path=\"tunnels/new\" element={gated('vibecontrols.tunnels', <TunnelCreateWizard />)} />\n <Route\n path=\"tunnels/:tunnelId\"\n element={gated('vibecontrols.tunnels', <TunnelDetailsPage />)}\n />\n\n {/* Insights */}\n <Route path=\"health\" element={gated('vibecontrols.health', <HealthPage />)} />\n <Route path=\"analytics\" element={gated('vibecontrols.analytics', <AnalyticsPage />)} />\n <Route path=\"audit\" element={gated('vibecontrols.audit-log', <VibeAuditPage />, false)} />\n <Route path=\"logs\" element={<LogsPage />} />\n\n {/* Settings */}\n <Route path=\"settings\" element={gated('vibecontrols.settings', <SettingsPage />)} />\n\n {/* Setup */}\n {/* Not gated — onboarding flow must always be reachable */}\n <Route path=\"getting-started\" element={<GettingStartedPage />} />\n\n {/* Shared public routes (no gating — accessible via public links) */}\n <Route path=\"share/:token\" element={<SharedSessionPage />} />\n <Route path=\"deck/:token\" element={<SharedVibeDeckPage />} />\n\n {/* Legacy redirects */}\n <Route path=\"access\" element={<Navigate to=\"../agents?tab=targets\" replace />} />\n <Route path=\"ssh-targets\" element={<Navigate to=\"../agents?tab=targets\" replace />} />\n <Route path=\"ssh-terminals\" element={<Navigate to=\"../agents?tab=targets\" replace />} />\n\n {/* Catch-all route - redirect to overview */}\n <Route path=\"*\" element={<VibeNotFound />} />\n </Routes>\n </Suspense>\n );\n}\n"],"mappings":"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"}
|
|
@@ -44,7 +44,7 @@ var o = {
|
|
|
44
44
|
return n === e ? t : n;
|
|
45
45
|
}, h = o[c], g = !!d || !!f, _ = e(c, u), v = [];
|
|
46
46
|
_.phase === "none" ? u && (c === "DEGRADED" || c === "OFFLINE") && v.push(`${h.label}: ${u}`) : (v.push(`${_.label} — ${_.actionHint}`), u && v.push(`Details: ${u}`)), g && v.push(m("agents.statusBadge.connectionLogHint", s));
|
|
47
|
-
let y = v.length > 0 ? v.join(" · ") : void 0, b = !!h.spinner || _.inProgress, x = `inline-flex items-center gap-1 px-2.5 py-0.5 rounded-full text-xs font-medium border transition-colors duration-150 ${h.color} ${l}`, S = g ? " cursor-pointer hover:opacity-90
|
|
47
|
+
let y = v.length > 0 ? v.join(" · ") : void 0, b = !!h.spinner || _.inProgress, x = `inline-flex items-center gap-1 px-2.5 py-0.5 rounded-full text-xs font-medium border transition-colors duration-150 ${h.color} ${l}`, S = g ? " cursor-pointer hover:opacity-90 transition-transform duration-100 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-strong" : "", C = /* @__PURE__ */ a(r, { children: [b && /* @__PURE__ */ i(n, {
|
|
48
48
|
className: "size-3 animate-spin",
|
|
49
49
|
"aria-hidden": "true"
|
|
50
50
|
}), h.label] });
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AgentStatusBadge.js","names":[],"sources":["../../../src/components/agents/AgentStatusBadge.tsx"],"sourcesContent":["import { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { Loader2 } from 'lucide-react';\nimport type { FC, MouseEvent as ReactMouseEvent } from 'react';\nimport type { AgentStatus } from './types';\nimport { classifyAgentHealth } from '../../utils/agentHealth';\n\nexport interface AgentStatusBadgeProps {\n status: AgentStatus;\n className?: string;\n /**\n * Optional probe error to expose as a hover tooltip (and aria-label).\n * Surfaces the actual reason a `DEGRADED` agent is unhealthy — most\n * commonly \"boot state awaiting-config\" when the gateway-auth push\n * during onboarding silently failed. Without this, users see a yellow\n * badge with no signal about WHY, and have to curl /health/ready\n * themselves to debug.\n */\n reason?: string | null;\n /**\n * Optional href that turns the badge into an anchor pointing at the\n * agent's Connection Log tab. When this is set the badge is keyboard-\n * focusable, supports Ctrl/Cmd-click to open in a new tab, and surfaces\n * a \"click to see connection log\" tooltip suffix so users know it's\n * actionable. Provide together with `onClick` if you also need a SPA\n * navigation handler — the anchor `href` will still be the source of\n * truth for the browser's new-tab affordance.\n */\n linkTo?: string;\n /**\n * SPA navigation callback fired on unmodified left-click. When the\n * caller is already on the agent's detail page this is typically a\n * `setActiveTab('connection')` call; when the caller is on a list view\n * it's typically a `navigate(detailUrl + '?tab=connection')` call. The\n * anchor still owns the modifier-key path so Ctrl-click opens the\n * destination in a new tab regardless.\n */\n onClick?: (event: ReactMouseEvent<HTMLAnchorElement>) => void;\n}\n\n// Subtle-recipe pills: a tinted background + same-hue saturated text + a\n// matching border. This passes WCAG AA in BOTH light and dark themes (unlike\n// the old solid `bg-status-X-bg` + `text-status-X-text`, which was same-hue\n// low-contrast). Transient states (registering/connecting) read NEUTRAL with a\n// spinner instead of off-brand blue — the spinner is the \"working\" signal.\nconst statusConfig: Record<AgentStatus, { label: string; color: string; spinner?: boolean }> = {\n REGISTERING: {\n label: 'Registering',\n color: 'bg-bg-sunken text-text-secondary border-border-default',\n spinner: true,\n },\n CONNECTING: {\n label: 'Connecting',\n color: 'bg-bg-sunken text-text-secondary border-border-default',\n spinner: true,\n },\n ACTIVE: {\n label: 'Active',\n color: 'bg-status-success-bg-subtle text-status-success-text border-status-success-border',\n },\n STOPPED: {\n label: 'Stopped',\n color: 'bg-status-warning-bg-subtle text-status-warning-text border-status-warning-border',\n },\n IDLE: {\n label: 'Idle',\n color: 'bg-status-warning-bg-subtle text-status-warning-text border-status-warning-border',\n },\n DEGRADED: {\n label: 'Degraded',\n color: 'bg-status-warning-bg-subtle text-status-warning-text border-status-warning-border',\n },\n OFFLINE: {\n label: 'Offline',\n color: 'bg-bg-sunken text-text-secondary border-border-default',\n },\n DELETED: {\n label: 'Deleted',\n color: 'bg-status-error-bg-subtle text-status-error-text border-status-error-border',\n },\n};\n\nconst CONNECTION_LOG_HINT = 'See connection log for details';\n\n/**\n * AgentStatusBadge - Display agent status with appropriate styling.\n *\n * When `linkTo` or `onClick` is supplied the badge renders as an anchor\n * and becomes the canonical entry point into the agent's Connection Log\n * tab. This keeps the user one click away from the timeline of probe /\n * register / gateway-auth-push events any time they spot a non-Active\n * badge.\n */\nexport const AgentStatusBadge: FC<AgentStatusBadgeProps> = ({\n status,\n className = '',\n reason,\n linkTo,\n onClick,\n}) => {\n const { t } = useI18n();\n const tr = (key: string, fallback: string) => {\n const v = t(key);\n return v === key ? fallback : v;\n };\n const config = statusConfig[status];\n const isInteractive = Boolean(linkTo) || Boolean(onClick);\n\n // Classify a degraded/offline agent so the badge can say whether work is\n // happening behind the scenes (spinner, \"no action needed\") or the user has\n // an action item — rather than just a yellow badge with a raw health string.\n const health = classifyAgentHealth(status, reason);\n\n const tooltipParts: string[] = [];\n if (health.phase !== 'none') {\n tooltipParts.push(`${health.label} — ${health.actionHint}`);\n if (reason) tooltipParts.push(`Details: ${reason}`);\n } else if (reason && (status === 'DEGRADED' || status === 'OFFLINE')) {\n tooltipParts.push(`${config.label}: ${reason}`);\n }\n if (isInteractive) {\n tooltipParts.push(tr('agents.statusBadge.connectionLogHint', CONNECTION_LOG_HINT));\n }\n const tooltip = tooltipParts.length > 0 ? tooltipParts.join(' · ') : undefined;\n\n // Show the in-progress spinner for CONNECTING and for a DEGRADED agent that\n // is still provisioning, so \"something is happening\" is visible at a glance.\n const showSpinner = Boolean(config.spinner) || health.inProgress;\n\n const baseClassName = `inline-flex items-center gap-1 px-2.5 py-0.5 rounded-full text-xs font-medium border transition-colors duration-150 ${config.color} ${className}`;\n const interactiveClassName = isInteractive\n ? ' cursor-pointer hover:opacity-90
|
|
1
|
+
{"version":3,"file":"AgentStatusBadge.js","names":[],"sources":["../../../src/components/agents/AgentStatusBadge.tsx"],"sourcesContent":["import { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { Loader2 } from 'lucide-react';\nimport type { FC, MouseEvent as ReactMouseEvent } from 'react';\nimport type { AgentStatus } from './types';\nimport { classifyAgentHealth } from '../../utils/agentHealth';\n\nexport interface AgentStatusBadgeProps {\n status: AgentStatus;\n className?: string;\n /**\n * Optional probe error to expose as a hover tooltip (and aria-label).\n * Surfaces the actual reason a `DEGRADED` agent is unhealthy — most\n * commonly \"boot state awaiting-config\" when the gateway-auth push\n * during onboarding silently failed. Without this, users see a yellow\n * badge with no signal about WHY, and have to curl /health/ready\n * themselves to debug.\n */\n reason?: string | null;\n /**\n * Optional href that turns the badge into an anchor pointing at the\n * agent's Connection Log tab. When this is set the badge is keyboard-\n * focusable, supports Ctrl/Cmd-click to open in a new tab, and surfaces\n * a \"click to see connection log\" tooltip suffix so users know it's\n * actionable. Provide together with `onClick` if you also need a SPA\n * navigation handler — the anchor `href` will still be the source of\n * truth for the browser's new-tab affordance.\n */\n linkTo?: string;\n /**\n * SPA navigation callback fired on unmodified left-click. When the\n * caller is already on the agent's detail page this is typically a\n * `setActiveTab('connection')` call; when the caller is on a list view\n * it's typically a `navigate(detailUrl + '?tab=connection')` call. The\n * anchor still owns the modifier-key path so Ctrl-click opens the\n * destination in a new tab regardless.\n */\n onClick?: (event: ReactMouseEvent<HTMLAnchorElement>) => void;\n}\n\n// Subtle-recipe pills: a tinted background + same-hue saturated text + a\n// matching border. This passes WCAG AA in BOTH light and dark themes (unlike\n// the old solid `bg-status-X-bg` + `text-status-X-text`, which was same-hue\n// low-contrast). Transient states (registering/connecting) read NEUTRAL with a\n// spinner instead of off-brand blue — the spinner is the \"working\" signal.\nconst statusConfig: Record<AgentStatus, { label: string; color: string; spinner?: boolean }> = {\n REGISTERING: {\n label: 'Registering',\n color: 'bg-bg-sunken text-text-secondary border-border-default',\n spinner: true,\n },\n CONNECTING: {\n label: 'Connecting',\n color: 'bg-bg-sunken text-text-secondary border-border-default',\n spinner: true,\n },\n ACTIVE: {\n label: 'Active',\n color: 'bg-status-success-bg-subtle text-status-success-text border-status-success-border',\n },\n STOPPED: {\n label: 'Stopped',\n color: 'bg-status-warning-bg-subtle text-status-warning-text border-status-warning-border',\n },\n IDLE: {\n label: 'Idle',\n color: 'bg-status-warning-bg-subtle text-status-warning-text border-status-warning-border',\n },\n DEGRADED: {\n label: 'Degraded',\n color: 'bg-status-warning-bg-subtle text-status-warning-text border-status-warning-border',\n },\n OFFLINE: {\n label: 'Offline',\n color: 'bg-bg-sunken text-text-secondary border-border-default',\n },\n DELETED: {\n label: 'Deleted',\n color: 'bg-status-error-bg-subtle text-status-error-text border-status-error-border',\n },\n};\n\nconst CONNECTION_LOG_HINT = 'See connection log for details';\n\n/**\n * AgentStatusBadge - Display agent status with appropriate styling.\n *\n * When `linkTo` or `onClick` is supplied the badge renders as an anchor\n * and becomes the canonical entry point into the agent's Connection Log\n * tab. This keeps the user one click away from the timeline of probe /\n * register / gateway-auth-push events any time they spot a non-Active\n * badge.\n */\nexport const AgentStatusBadge: FC<AgentStatusBadgeProps> = ({\n status,\n className = '',\n reason,\n linkTo,\n onClick,\n}) => {\n const { t } = useI18n();\n const tr = (key: string, fallback: string) => {\n const v = t(key);\n return v === key ? fallback : v;\n };\n const config = statusConfig[status];\n const isInteractive = Boolean(linkTo) || Boolean(onClick);\n\n // Classify a degraded/offline agent so the badge can say whether work is\n // happening behind the scenes (spinner, \"no action needed\") or the user has\n // an action item — rather than just a yellow badge with a raw health string.\n const health = classifyAgentHealth(status, reason);\n\n const tooltipParts: string[] = [];\n if (health.phase !== 'none') {\n tooltipParts.push(`${health.label} — ${health.actionHint}`);\n if (reason) tooltipParts.push(`Details: ${reason}`);\n } else if (reason && (status === 'DEGRADED' || status === 'OFFLINE')) {\n tooltipParts.push(`${config.label}: ${reason}`);\n }\n if (isInteractive) {\n tooltipParts.push(tr('agents.statusBadge.connectionLogHint', CONNECTION_LOG_HINT));\n }\n const tooltip = tooltipParts.length > 0 ? tooltipParts.join(' · ') : undefined;\n\n // Show the in-progress spinner for CONNECTING and for a DEGRADED agent that\n // is still provisioning, so \"something is happening\" is visible at a glance.\n const showSpinner = Boolean(config.spinner) || health.inProgress;\n\n const baseClassName = `inline-flex items-center gap-1 px-2.5 py-0.5 rounded-full text-xs font-medium border transition-colors duration-150 ${config.color} ${className}`;\n const interactiveClassName = isInteractive\n ? ' cursor-pointer hover:opacity-90 transition-transform duration-100 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-strong'\n : '';\n\n const content = (\n <>\n {showSpinner && <Loader2 className=\"size-3 animate-spin\" aria-hidden=\"true\" />}\n {config.label}\n </>\n );\n\n if (isInteractive) {\n return (\n <a\n href={linkTo ?? '#'}\n title={tooltip}\n aria-label={tooltip ?? config.label}\n className={baseClassName + interactiveClassName}\n onClick={(event) => {\n // Let modifier-clicks reach the browser's default tab-opener so\n // Ctrl/Cmd-click + middle-click open the connection log in a\n // new tab. SPA navigation only fires on plain left-click.\n if (event.metaKey || event.ctrlKey || event.shiftKey || event.button !== 0) {\n return;\n }\n if (onClick) {\n onClick(event);\n }\n }}\n >\n {content}\n </a>\n );\n }\n\n return (\n <span title={tooltip} aria-label={tooltip ?? config.label} className={baseClassName}>\n {content}\n </span>\n );\n};\n"],"mappings":";;;;;AA4CA,IAAM,IAAyF;CAC7F,aAAa;EACX,OAAO;EACP,OAAO;EACP,SAAS;EACV;CACD,YAAY;EACV,OAAO;EACP,OAAO;EACP,SAAS;EACV;CACD,QAAQ;EACN,OAAO;EACP,OAAO;EACR;CACD,SAAS;EACP,OAAO;EACP,OAAO;EACR;CACD,MAAM;EACJ,OAAO;EACP,OAAO;EACR;CACD,UAAU;EACR,OAAO;EACP,OAAO;EACR;CACD,SAAS;EACP,OAAO;EACP,OAAO;EACR;CACD,SAAS;EACP,OAAO;EACP,OAAO;EACR;CACF,EAEK,IAAsB,kCAWf,KAA+C,EAC1D,WACA,eAAY,IACZ,WACA,WACA,iBACI;CACJ,IAAM,EAAE,SAAM,GAAS,EACjB,KAAM,GAAa,MAAqB;EAC5C,IAAM,IAAI,EAAE,EAAI;AAChB,SAAO,MAAM,IAAM,IAAW;IAE1B,IAAS,EAAa,IACtB,IAAgB,EAAQ,KAAW,EAAQ,GAK3C,IAAS,EAAoB,GAAQ,EAAO,EAE5C,IAAyB,EAAE;AAOjC,CANI,EAAO,UAAU,SAGV,MAAW,MAAW,cAAc,MAAW,cACxD,EAAa,KAAK,GAAG,EAAO,MAAM,IAAI,IAAS,IAH/C,EAAa,KAAK,GAAG,EAAO,MAAM,KAAK,EAAO,aAAa,EACvD,KAAQ,EAAa,KAAK,YAAY,IAAS,GAIjD,KACF,EAAa,KAAK,EAAG,wCAAwC,EAAoB,CAAC;CAEpF,IAAM,IAAU,EAAa,SAAS,IAAI,EAAa,KAAK,MAAM,GAAG,KAAA,GAI/D,IAAc,EAAQ,EAAO,WAAY,EAAO,YAEhD,IAAgB,uHAAuH,EAAO,MAAM,GAAG,KACvJ,IAAuB,IACzB,wJACA,IAEE,IACJ,kBAAA,GAAA,EAAA,UAAA,CACG,KAAe,kBAAC,GAAD;EAAS,WAAU;EAAsB,eAAY;EAAS,CAAA,EAC7E,EAAO,MACP,EAAA,CAAA;AA2BL,QAxBI,IAEA,kBAAC,KAAD;EACE,MAAM,KAAU;EAChB,OAAO;EACP,cAAY,KAAW,EAAO;EAC9B,WAAW,IAAgB;EAC3B,UAAU,MAAU;AAId,KAAM,WAAW,EAAM,WAAW,EAAM,YAAY,EAAM,WAAW,KAGrE,KACF,EAAQ,EAAM;;YAIjB;EACC,CAAA,GAKN,kBAAC,QAAD;EAAM,OAAO;EAAS,cAAY,KAAW,EAAO;EAAO,WAAW;YACnE;EACI,CAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ConnectionLogPanel.js","names":[],"sources":["../../../src/components/agents/ConnectionLogPanel.tsx"],"sourcesContent":["import { useCallback, useMemo, useState } from 'react';\nimport { Check, Copy, Download, FileJson, RefreshCw } from 'lucide-react';\nimport {\n useGetAgentConnectionEventsQuery,\n useGetSessionConnectionEventsQuery,\n} from '@/generated/wspace-operations';\nimport { useTr } from '../../shared/hooks/useTr';\nimport { ActionMenu } from '../shared/ActionMenu';\n\ninterface Props {\n /** Pass agentId XOR sessionId. */\n agentId?: string;\n sessionId?: string;\n /** Friendly label shown in the export filename. */\n agentName?: string;\n sessionName?: string;\n}\n\ninterface EventRow {\n id: string;\n timestamp: string;\n sequence: number;\n step: string;\n level: string;\n message: string;\n detailsJson?: string | null;\n}\n\nconst LEVEL_COLOR: Record<string, string> = {\n info: 'text-text-secondary',\n warn: 'text-status-warning-text',\n error: 'text-status-error-text',\n debug: 'text-text-muted',\n};\n\nconst LEVEL_BG: Record<string, string> = {\n info: 'bg-bg-sunken',\n warn: 'bg-status-warning-bg-subtle',\n error: 'bg-status-error-bg-subtle',\n debug: 'bg-bg-muted',\n};\n\nfunction formatTimestamp(iso: string): string {\n try {\n return new Date(iso).toISOString().replace('T', ' ').replace('Z', '');\n } catch {\n return iso;\n }\n}\n\nfunction eventsAsText(rows: EventRow[]): string {\n return rows\n .map((e) => {\n const detail = e.detailsJson ? `\\n details: ${e.detailsJson}` : '';\n return `[${e.timestamp}] #${e.sequence} ${e.level.toUpperCase().padEnd(5)} ${e.step.padEnd(28)} ${e.message}${detail}`;\n })\n .join('\\n');\n}\n\nfunction eventsAsJson(rows: EventRow[], agentId: string, agentName?: string): string {\n return JSON.stringify(\n {\n exportedAt: new Date().toISOString(),\n agentId,\n agentName,\n count: rows.length,\n events: rows.map((e) => ({\n id: e.id,\n timestamp: e.timestamp,\n sequence: e.sequence,\n step: e.step,\n level: e.level,\n message: e.message,\n details: e.detailsJson ? JSON.parse(e.detailsJson) : null,\n })),\n },\n null,\n 2\n );\n}\n\nfunction downloadBlob(filename: string, mime: string, body: string): void {\n const blob = new Blob([body], { type: mime });\n const url = URL.createObjectURL(blob);\n const a = document.createElement('a');\n a.href = url;\n a.download = filename;\n document.body.appendChild(a);\n a.click();\n document.body.removeChild(a);\n URL.revokeObjectURL(url);\n}\n\nconst ICON_BTN =\n 'inline-flex size-9 max-md:size-11 items-center justify-center rounded-button border border-border-default bg-bg-sunken text-text-primary transition-colors hover:bg-bg-elevated focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus';\n\nexport function ConnectionLogPanel({ agentId, sessionId, agentName, sessionName }: Props) {\n // Poll every 3s while the panel is mounted so the operator sees new\n // events appear as the svc records them. The same panel powers both\n // the AgentDetails \"Connection Log\" tab (pass agentId) and the\n // SessionDetails \"Connection Log\" tab (pass sessionId). Apollo\n // skips the query whose id is missing.\n const tr = useTr();\n const isAgent = !!agentId;\n const subjectId = (agentId ?? sessionId) || '';\n const subjectLabel = isAgent ? agentName : sessionName;\n\n const agentQuery = useGetAgentConnectionEventsQuery({\n variables: { agentId: agentId ?? '', limit: 200 },\n pollInterval: 3000,\n fetchPolicy: 'cache-and-network',\n skip: !agentId,\n });\n const sessionQuery = useGetSessionConnectionEventsQuery({\n variables: { sessionId: sessionId ?? '', limit: 200 },\n pollInterval: 3000,\n fetchPolicy: 'cache-and-network',\n skip: !sessionId,\n });\n const { loading, error, refetch } = isAgent ? agentQuery : sessionQuery;\n const [copied, setCopied] = useState<'none' | 'text' | 'json'>('none');\n\n const events: EventRow[] = useMemo(() => {\n const rawList: Array<{\n id: string;\n timestamp: string;\n sequence: number;\n step: string;\n level: string;\n message: string;\n detailsJson?: string | null;\n }> = isAgent\n ? (agentQuery.data?.vibecontrolsAgentConnectionEvents ?? [])\n : (sessionQuery.data?.vibecontrolsSessionConnectionEvents ?? []);\n return rawList.map((e) => ({\n id: e.id,\n timestamp: e.timestamp,\n sequence: e.sequence,\n step: e.step,\n level: e.level,\n message: e.message,\n detailsJson: e.detailsJson ?? null,\n }));\n }, [isAgent, agentQuery.data, sessionQuery.data]);\n\n const handleCopy = useCallback(\n async (mode: 'text' | 'json') => {\n const body =\n mode === 'text' ? eventsAsText(events) : eventsAsJson(events, subjectId, subjectLabel);\n try {\n await navigator.clipboard.writeText(body);\n setCopied(mode);\n setTimeout(() => setCopied('none'), 1500);\n } catch {\n const filename = `${isAgent ? 'agent' : 'session'}-${subjectId}-connection-log.${\n mode === 'text' ? 'txt' : 'json'\n }`;\n downloadBlob(filename, mode === 'text' ? 'text/plain' : 'application/json', body);\n }\n },\n [events, subjectId, subjectLabel, isAgent]\n );\n\n const handleDownload = useCallback(\n (mode: 'text' | 'json') => {\n const body =\n mode === 'text' ? eventsAsText(events) : eventsAsJson(events, subjectId, subjectLabel);\n const stamp = new Date().toISOString().replace(/[:.]/g, '-');\n const prefix = isAgent ? 'agent' : 'session';\n downloadBlob(\n `${prefix}-${subjectLabel ?? subjectId}-connection-log-${stamp}.${mode === 'text' ? 'txt' : 'json'}`,\n mode === 'text' ? 'text/plain' : 'application/json',\n body\n );\n },\n [events, subjectId, subjectLabel, isAgent]\n );\n\n return (\n <div className=\"flex flex-col h-full\">\n <div className=\"flex flex-wrap items-center justify-between gap-3 p-4 border-b border-border-default\">\n <div className=\"min-w-0\">\n <h3 className=\"text-text-primary font-semibold text-base\">Connection Log</h3>\n <p role=\"status\" aria-live=\"polite\" className=\"text-text-muted text-xs mt-0.5\">\n {loading && events.length === 0\n ? 'Loading…'\n : `${events.length} event${events.length === 1 ? '' : 's'} · auto-refresh every 3s`}\n </p>\n </div>\n <div className=\"flex flex-wrap items-center gap-2\">\n <button\n type=\"button\"\n onClick={() => refetch()}\n aria-label={tr('connectionLog.refresh', 'Refresh connection log')}\n className={ICON_BTN}\n >\n <RefreshCw className=\"size-4\" aria-hidden=\"true\" />\n </button>\n <button\n type=\"button\"\n onClick={() => handleCopy('text')}\n aria-label={\n copied === 'text'\n ? tr('connectionLog.copied', 'Copied')\n : tr('connectionLog.copyText', 'Copy log as text')\n }\n className={ICON_BTN}\n >\n {copied === 'text' ? (\n <Check className=\"size-4 text-status-success-text\" aria-hidden=\"true\" />\n ) : (\n <Copy className=\"size-4\" aria-hidden=\"true\" />\n )}\n </button>\n <ActionMenu\n ariaLabel={tr('connectionLog.moreActions', 'More log actions')}\n title={tr('connectionLog.moreActions', 'More log actions')}\n align=\"end\"\n triggerClassName={ICON_BTN}\n items={[\n {\n key: 'copy-json',\n label:\n copied === 'json'\n ? tr('connectionLog.copied', 'Copied')\n : tr('connectionLog.copyJson', 'Copy JSON'),\n icon: Copy,\n onSelect: () => void handleCopy('json'),\n },\n {\n key: 'export-txt',\n label: tr('connectionLog.exportTxt', 'Export .txt'),\n icon: Download,\n onSelect: () => handleDownload('text'),\n separatorBefore: true,\n },\n {\n key: 'export-json',\n label: tr('connectionLog.exportJson', 'Export .json'),\n icon: FileJson,\n onSelect: () => handleDownload('json'),\n },\n ]}\n />\n </div>\n </div>\n\n {error && (\n <div\n role=\"alert\"\n className=\"m-4 p-3 rounded bg-status-error-bg-subtle border border-status-error-border text-status-error-text text-sm\"\n >\n Failed to load events: {error.message}\n </div>\n )}\n\n <div className=\"flex-1 min-h-0 overflow-auto p-4\">\n {events.length === 0 && !loading && (\n <div className=\"text-text-muted text-sm text-center py-12\">\n No connection events recorded for this agent yet.\n <br />\n Events appear here every time the platform probes, registers, or syncs the agent.\n </div>\n )}\n <ol className=\"space-y-2\">\n {events.map((e) => {\n const levelColor = LEVEL_COLOR[e.level] ?? LEVEL_COLOR.info;\n const levelBg = LEVEL_BG[e.level] ?? LEVEL_BG.info;\n const details = (() => {\n if (!e.detailsJson) return null;\n try {\n return JSON.stringify(JSON.parse(e.detailsJson), null, 2);\n } catch {\n return e.detailsJson;\n }\n })();\n return (\n <li key={e.id} className=\"rounded border border-border-default bg-bg-sunken\">\n <div className=\"flex items-start gap-3 p-3\">\n <div\n className={`px-2 py-0.5 rounded text-xs uppercase font-mono ${levelBg} ${levelColor} shrink-0`}\n >\n {e.level}\n </div>\n <div className=\"flex-1 min-w-0\">\n <div className=\"flex flex-wrap items-center gap-2 text-xs text-text-muted font-mono\">\n <span>#{e.sequence}</span>\n <span aria-hidden=\"true\">·</span>\n <span>{formatTimestamp(e.timestamp)}</span>\n <span aria-hidden=\"true\">·</span>\n <span className=\"text-text-primary font-medium\">{e.step}</span>\n </div>\n <div className=\"text-sm text-text-primary mt-1 break-words\">{e.message}</div>\n {details && (\n <details className=\"mt-2\">\n <summary className=\"text-xs text-text-muted cursor-pointer hover:text-text-primary\">\n Details\n </summary>\n <pre className=\"mt-2 p-2 rounded bg-bg-surface text-xs text-text-primary overflow-x-auto whitespace-pre-wrap break-words\">\n {details}\n </pre>\n </details>\n )}\n </div>\n </div>\n </li>\n );\n })}\n </ol>\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ConnectionLogPanel.js","names":[],"sources":["../../../src/components/agents/ConnectionLogPanel.tsx"],"sourcesContent":["import { useCallback, useMemo, useState } from 'react';\nimport { Check, Copy, Download, FileJson, RefreshCw } from 'lucide-react';\nimport {\n useGetAgentConnectionEventsQuery,\n useGetSessionConnectionEventsQuery,\n} from '@/generated/wspace-operations';\nimport { useTr } from '../../shared/hooks/useTr';\nimport { ActionMenu } from '../shared/ActionMenu';\n\ninterface Props {\n /** Pass agentId XOR sessionId. */\n agentId?: string;\n sessionId?: string;\n /** Friendly label shown in the export filename. */\n agentName?: string;\n sessionName?: string;\n}\n\ninterface EventRow {\n id: string;\n timestamp: string;\n sequence: number;\n step: string;\n level: string;\n message: string;\n detailsJson?: string | null;\n}\n\nconst LEVEL_COLOR: Record<string, string> = {\n info: 'text-text-secondary',\n warn: 'text-status-warning-text',\n error: 'text-status-error-text',\n debug: 'text-text-muted',\n};\n\nconst LEVEL_BG: Record<string, string> = {\n info: 'bg-bg-sunken',\n warn: 'bg-status-warning-bg-subtle',\n error: 'bg-status-error-bg-subtle',\n debug: 'bg-bg-sunken',\n};\n\nfunction formatTimestamp(iso: string): string {\n try {\n return new Date(iso).toISOString().replace('T', ' ').replace('Z', '');\n } catch {\n return iso;\n }\n}\n\nfunction eventsAsText(rows: EventRow[]): string {\n return rows\n .map((e) => {\n const detail = e.detailsJson ? `\\n details: ${e.detailsJson}` : '';\n return `[${e.timestamp}] #${e.sequence} ${e.level.toUpperCase().padEnd(5)} ${e.step.padEnd(28)} ${e.message}${detail}`;\n })\n .join('\\n');\n}\n\nfunction eventsAsJson(rows: EventRow[], agentId: string, agentName?: string): string {\n return JSON.stringify(\n {\n exportedAt: new Date().toISOString(),\n agentId,\n agentName,\n count: rows.length,\n events: rows.map((e) => ({\n id: e.id,\n timestamp: e.timestamp,\n sequence: e.sequence,\n step: e.step,\n level: e.level,\n message: e.message,\n details: e.detailsJson ? JSON.parse(e.detailsJson) : null,\n })),\n },\n null,\n 2\n );\n}\n\nfunction downloadBlob(filename: string, mime: string, body: string): void {\n const blob = new Blob([body], { type: mime });\n const url = URL.createObjectURL(blob);\n const a = document.createElement('a');\n a.href = url;\n a.download = filename;\n document.body.appendChild(a);\n a.click();\n document.body.removeChild(a);\n URL.revokeObjectURL(url);\n}\n\nconst ICON_BTN =\n 'inline-flex size-9 max-md:size-11 items-center justify-center rounded-button border border-border-default bg-bg-sunken text-text-primary transition-colors hover:bg-bg-elevated focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus';\n\nexport function ConnectionLogPanel({ agentId, sessionId, agentName, sessionName }: Props) {\n // Poll every 3s while the panel is mounted so the operator sees new\n // events appear as the svc records them. The same panel powers both\n // the AgentDetails \"Connection Log\" tab (pass agentId) and the\n // SessionDetails \"Connection Log\" tab (pass sessionId). Apollo\n // skips the query whose id is missing.\n const tr = useTr();\n const isAgent = !!agentId;\n const subjectId = (agentId ?? sessionId) || '';\n const subjectLabel = isAgent ? agentName : sessionName;\n\n const agentQuery = useGetAgentConnectionEventsQuery({\n variables: { agentId: agentId ?? '', limit: 200 },\n pollInterval: 3000,\n fetchPolicy: 'cache-and-network',\n skip: !agentId,\n });\n const sessionQuery = useGetSessionConnectionEventsQuery({\n variables: { sessionId: sessionId ?? '', limit: 200 },\n pollInterval: 3000,\n fetchPolicy: 'cache-and-network',\n skip: !sessionId,\n });\n const { loading, error, refetch } = isAgent ? agentQuery : sessionQuery;\n const [copied, setCopied] = useState<'none' | 'text' | 'json'>('none');\n\n const events: EventRow[] = useMemo(() => {\n const rawList: Array<{\n id: string;\n timestamp: string;\n sequence: number;\n step: string;\n level: string;\n message: string;\n detailsJson?: string | null;\n }> = isAgent\n ? (agentQuery.data?.vibecontrolsAgentConnectionEvents ?? [])\n : (sessionQuery.data?.vibecontrolsSessionConnectionEvents ?? []);\n return rawList.map((e) => ({\n id: e.id,\n timestamp: e.timestamp,\n sequence: e.sequence,\n step: e.step,\n level: e.level,\n message: e.message,\n detailsJson: e.detailsJson ?? null,\n }));\n }, [isAgent, agentQuery.data, sessionQuery.data]);\n\n const handleCopy = useCallback(\n async (mode: 'text' | 'json') => {\n const body =\n mode === 'text' ? eventsAsText(events) : eventsAsJson(events, subjectId, subjectLabel);\n try {\n await navigator.clipboard.writeText(body);\n setCopied(mode);\n setTimeout(() => setCopied('none'), 1500);\n } catch {\n const filename = `${isAgent ? 'agent' : 'session'}-${subjectId}-connection-log.${\n mode === 'text' ? 'txt' : 'json'\n }`;\n downloadBlob(filename, mode === 'text' ? 'text/plain' : 'application/json', body);\n }\n },\n [events, subjectId, subjectLabel, isAgent]\n );\n\n const handleDownload = useCallback(\n (mode: 'text' | 'json') => {\n const body =\n mode === 'text' ? eventsAsText(events) : eventsAsJson(events, subjectId, subjectLabel);\n const stamp = new Date().toISOString().replace(/[:.]/g, '-');\n const prefix = isAgent ? 'agent' : 'session';\n downloadBlob(\n `${prefix}-${subjectLabel ?? subjectId}-connection-log-${stamp}.${mode === 'text' ? 'txt' : 'json'}`,\n mode === 'text' ? 'text/plain' : 'application/json',\n body\n );\n },\n [events, subjectId, subjectLabel, isAgent]\n );\n\n return (\n <div className=\"flex flex-col h-full\">\n <div className=\"flex flex-wrap items-center justify-between gap-3 p-4 border-b border-border-default\">\n <div className=\"min-w-0\">\n <h3 className=\"text-text-primary font-semibold text-base\">Connection Log</h3>\n <p role=\"status\" aria-live=\"polite\" className=\"text-text-muted text-xs mt-0.5\">\n {loading && events.length === 0\n ? 'Loading…'\n : `${events.length} event${events.length === 1 ? '' : 's'} · auto-refresh every 3s`}\n </p>\n </div>\n <div className=\"flex flex-wrap items-center gap-2\">\n <button\n type=\"button\"\n onClick={() => refetch()}\n aria-label={tr('connectionLog.refresh', 'Refresh connection log')}\n className={ICON_BTN}\n >\n <RefreshCw className=\"size-4\" aria-hidden=\"true\" />\n </button>\n <button\n type=\"button\"\n onClick={() => handleCopy('text')}\n aria-label={\n copied === 'text'\n ? tr('connectionLog.copied', 'Copied')\n : tr('connectionLog.copyText', 'Copy log as text')\n }\n className={ICON_BTN}\n >\n {copied === 'text' ? (\n <Check className=\"size-4 text-status-success-text\" aria-hidden=\"true\" />\n ) : (\n <Copy className=\"size-4\" aria-hidden=\"true\" />\n )}\n </button>\n <ActionMenu\n ariaLabel={tr('connectionLog.moreActions', 'More log actions')}\n title={tr('connectionLog.moreActions', 'More log actions')}\n align=\"end\"\n triggerClassName={ICON_BTN}\n items={[\n {\n key: 'copy-json',\n label:\n copied === 'json'\n ? tr('connectionLog.copied', 'Copied')\n : tr('connectionLog.copyJson', 'Copy JSON'),\n icon: Copy,\n onSelect: () => void handleCopy('json'),\n },\n {\n key: 'export-txt',\n label: tr('connectionLog.exportTxt', 'Export .txt'),\n icon: Download,\n onSelect: () => handleDownload('text'),\n separatorBefore: true,\n },\n {\n key: 'export-json',\n label: tr('connectionLog.exportJson', 'Export .json'),\n icon: FileJson,\n onSelect: () => handleDownload('json'),\n },\n ]}\n />\n </div>\n </div>\n\n {error && (\n <div\n role=\"alert\"\n className=\"m-4 p-3 rounded bg-status-error-bg-subtle border border-status-error-border text-status-error-text text-sm\"\n >\n Failed to load events: {error.message}\n </div>\n )}\n\n <div className=\"flex-1 min-h-0 overflow-auto p-4\">\n {events.length === 0 && !loading && (\n <div className=\"text-text-muted text-sm text-center py-12\">\n No connection events recorded for this agent yet.\n <br />\n Events appear here every time the platform probes, registers, or syncs the agent.\n </div>\n )}\n <ol className=\"space-y-2\">\n {events.map((e) => {\n const levelColor = LEVEL_COLOR[e.level] ?? LEVEL_COLOR.info;\n const levelBg = LEVEL_BG[e.level] ?? LEVEL_BG.info;\n const details = (() => {\n if (!e.detailsJson) return null;\n try {\n return JSON.stringify(JSON.parse(e.detailsJson), null, 2);\n } catch {\n return e.detailsJson;\n }\n })();\n return (\n <li key={e.id} className=\"rounded border border-border-default bg-bg-sunken\">\n <div className=\"flex items-start gap-3 p-3\">\n <div\n className={`px-2 py-0.5 rounded text-xs uppercase font-mono ${levelBg} ${levelColor} shrink-0`}\n >\n {e.level}\n </div>\n <div className=\"flex-1 min-w-0\">\n <div className=\"flex flex-wrap items-center gap-2 text-xs text-text-muted font-mono\">\n <span>#{e.sequence}</span>\n <span aria-hidden=\"true\">·</span>\n <span>{formatTimestamp(e.timestamp)}</span>\n <span aria-hidden=\"true\">·</span>\n <span className=\"text-text-primary font-medium\">{e.step}</span>\n </div>\n <div className=\"text-sm text-text-primary mt-1 break-words\">{e.message}</div>\n {details && (\n <details className=\"mt-2\">\n <summary className=\"text-xs text-text-muted cursor-pointer hover:text-text-primary\">\n Details\n </summary>\n <pre className=\"mt-2 p-2 rounded bg-bg-surface text-xs text-text-primary overflow-x-auto whitespace-pre-wrap break-words\">\n {details}\n </pre>\n </details>\n )}\n </div>\n </div>\n </li>\n );\n })}\n </ol>\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -383,7 +383,7 @@ function y({ availablePlugins: y, tunnelUrl: b, agentId: x, profile: S }) {
|
|
|
383
383
|
/* @__PURE__ */ m("button", {
|
|
384
384
|
type: "button",
|
|
385
385
|
onClick: () => I(e.id),
|
|
386
|
-
className: "mt-4 px-4 py-2 bg-bg-sunken text-text-primary text-sm rounded-md hover:bg-bg-
|
|
386
|
+
className: "mt-4 px-4 py-2 bg-bg-sunken text-text-primary text-sm rounded-md hover:bg-bg-sunken transition-colors inline-flex items-center gap-2 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus",
|
|
387
387
|
children: [/* @__PURE__ */ p(c, {
|
|
388
388
|
className: "size-3.5",
|
|
389
389
|
"aria-hidden": "true"
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PluginUIPanel.js","names":[],"sources":["../../../src/components/agents/PluginUIPanel.tsx"],"sourcesContent":["import {\n useState,\n useCallback,\n useRef,\n useEffect,\n useId,\n type KeyboardEvent as ReactKeyboardEvent,\n type PointerEvent as ReactPointerEvent,\n} from 'react';\nimport { useMintAgentCapabilityMutation } from '@/generated/wspace-operations';\nimport { AgentCapabilityScope } from '@/generated/wspace-types';\nimport {\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuTrigger,\n} from '@burdenoff/fe-libs/ui';\nimport {\n X,\n Maximize2,\n Minimize2,\n ExternalLink,\n Loader2,\n AlertCircle,\n RotateCcw,\n Wifi,\n WifiOff,\n GripVertical,\n Plus,\n Monitor,\n ChevronDown,\n} from 'lucide-react';\nimport { useTr } from '../../shared/hooks/useTr';\n\n// ── Types ──────────────────────────────────────────────────────────────\n\nexport interface PluginUITab {\n /** Unique tab ID (allows duplicate plugins in multiple tabs) */\n id: string;\n /** The plugin name (e.g. \"ui-ai\") — may be null if nothing selected yet */\n pluginName: string | null;\n /** Display name shown on the tab */\n name: string;\n /** The full URL to load in the iframe */\n uiUrl: string | null;\n}\n\nexport interface UIPluginOption {\n /** The plugin name key (e.g. \"ui-ai\") */\n pluginName: string;\n /** Display name */\n displayName: string;\n /** Description shown in dropdown */\n description?: string;\n /** Version */\n version?: string;\n /** Custom URL to use instead of the default /ui/{pluginName} path (e.g. for code-server proxy) */\n customUrl?: string;\n}\n\ninterface PluginUIPanelProps {\n /** Available UI plugins the user can pick from */\n availablePlugins: UIPluginOption[];\n /** Base URL of the agent tunnel (e.g. \"https://xxx.trycloudflare.com\") */\n tunnelUrl: string | null;\n /**\n * Agent ID — F4 Wave B replaced the legacy `?apiKey=` URL embed +\n * postMessage handoff with `mintAgentCapability(IFRAME_PLUGIN)`. Each\n * plugin tab loads with `#vt=<token>` so the long-lived agent secret\n * never reaches the browser.\n */\n agentId: string | null;\n /** Canonical CLI profile name; required for capability mint scoping. */\n profile: string;\n}\n\nconst MIN_PANEL_HEIGHT = 320; // matches min-h-[20rem]\nconst RESIZE_KEY_STEP = 32;\n\nfunction clampHeight(height: number): number {\n const max = typeof window !== 'undefined' ? window.innerHeight - 150 : 800;\n return Math.max(MIN_PANEL_HEIGHT, Math.min(height, max));\n}\n\nconst TOOLBAR_BTN =\n 'inline-flex size-7 max-md:size-9 items-center justify-center rounded-button text-text-secondary transition-colors hover:bg-bg-surface/50 hover:text-text-primary focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus';\n\n/**\n * Embedded browser-like panel for plugin UIs.\n * Each tab has a dropdown to select which plugin to load. The same plugin can\n * be loaded in multiple tabs, each with its own navigation state.\n */\nexport function PluginUIPanel({\n availablePlugins,\n tunnelUrl,\n agentId,\n profile,\n}: PluginUIPanelProps) {\n const [mintAgentCapability] = useMintAgentCapabilityMutation();\n const tr = useTr();\n\n // Tab ids: useId prefix + per-instance counter — no module-global state.\n const idPrefix = useId();\n const tabCounterRef = useRef(0);\n const nextTabId = useCallback((): string => {\n tabCounterRef.current += 1;\n return `${idPrefix}-ui-tab-${tabCounterRef.current}`;\n }, [idPrefix]);\n\n const [tabs, setTabs] = useState<PluginUITab[]>(() => {\n // Start with one blank tab\n return [{ id: nextTabId(), pluginName: null, name: 'New Tab', uiUrl: null }];\n });\n const [activeTabId, setActiveTabId] = useState<string>(tabs[0].id);\n const [maximized, setMaximized] = useState(false);\n\n // Connection status per tab\n const [connectionStatus, setConnectionStatus] = useState<\n Record<string, 'connecting' | 'connected' | 'error'>\n >({});\n\n // Dropdown open state per tab\n const [dropdownOpen, setDropdownOpen] = useState<string | null>(null);\n\n // Drag-to-resize state\n const [resizeHeight, setResizeHeight] = useState<number | null>(null);\n const [isResizing, setIsResizing] = useState(false);\n const [measuredHeight, setMeasuredHeight] = useState<number | null>(null);\n const startYRef = useRef(0);\n const startHeightRef = useRef(0);\n const contentRef = useRef<HTMLDivElement>(null);\n\n // Iframe refs for reload; tab refs for roving-tabindex arrow navigation\n const iframeRefs = useRef<Record<string, HTMLIFrameElement | null>>({});\n const tabRefs = useRef<Record<string, HTMLDivElement | null>>({});\n\n const activeTab = tabs.find((t) => t.id === activeTabId) || tabs[0] || null;\n\n // Build UI URL for a plugin — F4 Wave B: append a short-lived\n // IFRAME_PLUGIN capability as `#vt=<token>` so the agent's `/ui/<name>`\n // bootstrap can exchange it for a scoped HttpOnly cookie. No\n // long-lived secret in the URL or postMessage.\n const buildUiUrl = useCallback(\n async (pluginName: string, customUrl?: string): Promise<string | null> => {\n if (!tunnelUrl || !agentId) return null;\n const base = tunnelUrl.replace(/\\/$/, '');\n\n // Use customUrl if provided (e.g. for code-server proxy path)\n const urlStr = customUrl\n ? customUrl.startsWith('http')\n ? customUrl\n : `${base}${customUrl}`\n : `${base}/ui/${pluginName}`;\n\n const finalUrl = new URL(urlStr).toString();\n\n try {\n const res = await mintAgentCapability({\n variables: {\n agentId,\n profile: profile ?? 'default',\n scope: AgentCapabilityScope.IframePlugin,\n // Pass the bare plugin name — the svc maps to `/ui/<plugin>`\n // which matches the agent's iframe-token allow-list. Previous\n // `ui:<plugin>` form produced an invalid `/ui/ui:<plugin>`\n // prefix that the agent now rejects (audit-B P0-SEC-03).\n resourceId: pluginName,\n ttlSeconds: 60,\n },\n });\n const token = res.data?.mintAgentCapability?.token;\n if (!token) return finalUrl;\n // code-server's reverse proxy reads the capability from the `?vt=`\n // QUERY (GET-only) and consumes it server-side to mint its session\n // cookie — it has no client-side fragment-exchange shim. `/ui/<plugin>`\n // (and `/terminal`) DO have that shim, so they keep the `#vt=`\n // fragment, which is never sent to proxies/logs/Referer.\n const builtUrl = new URL(finalUrl);\n const isCodeServer =\n pluginName === 'code-server' || builtUrl.pathname.startsWith('/code-server');\n if (isCodeServer) {\n builtUrl.searchParams.set('vt', token);\n return builtUrl.toString();\n }\n return `${finalUrl}#vt=${encodeURIComponent(token)}`;\n } catch {\n return finalUrl;\n }\n },\n [tunnelUrl, agentId, profile, mintAgentCapability]\n );\n\n // Display name for a plugin name\n const getDisplayName = useCallback(\n (pluginName: string): string => {\n const opt = availablePlugins.find((p) => p.pluginName === pluginName);\n return (\n opt?.displayName ||\n pluginName\n .replace(/^ui-/, '')\n .replace(/-/g, ' ')\n .replace(/\\b\\w/g, (c) => c.toUpperCase())\n );\n },\n [availablePlugins]\n );\n\n // ── Tab Actions ───────────────────────────────────────────────────\n\n const handleAddTab = useCallback(() => {\n const newTab: PluginUITab = {\n id: nextTabId(),\n pluginName: null,\n name: 'New Tab',\n uiUrl: null,\n };\n setTabs((prev) => [...prev, newTab]);\n setActiveTabId(newTab.id);\n setDropdownOpen(newTab.id);\n }, [nextTabId]);\n\n const handleCloseTab = useCallback(\n (tabId: string) => {\n setTabs((prev) => {\n const next = prev.filter((t) => t.id !== tabId);\n // If we closed the last tab, add a blank one\n if (next.length === 0) {\n const blank: PluginUITab = {\n id: nextTabId(),\n pluginName: null,\n name: 'New Tab',\n uiUrl: null,\n };\n next.push(blank);\n setActiveTabId(blank.id);\n } else if (activeTabId === tabId) {\n // Select the previous tab or the first one\n const closedIdx = prev.findIndex((t) => t.id === tabId);\n const newIdx = Math.max(0, closedIdx - 1);\n setActiveTabId(next[Math.min(newIdx, next.length - 1)].id);\n }\n return next;\n });\n // Clean up refs\n delete iframeRefs.current[tabId];\n delete tabRefs.current[tabId];\n setConnectionStatus((prev) => {\n const next = { ...prev };\n delete next[tabId];\n return next;\n });\n },\n [activeTabId, nextTabId]\n );\n\n const handleSelectPlugin = useCallback(\n async (tabId: string, pluginName: string) => {\n const opt = availablePlugins.find((p) => p.pluginName === pluginName);\n const displayName = getDisplayName(pluginName);\n setConnectionStatus((prev) => ({ ...prev, [tabId]: 'connecting' }));\n setDropdownOpen(null);\n const uiUrl = await buildUiUrl(pluginName, opt?.customUrl);\n setTabs((prev) =>\n prev.map((t) => (t.id === tabId ? { ...t, pluginName, name: displayName, uiUrl } : t))\n );\n },\n [availablePlugins, buildUiUrl, getDisplayName]\n );\n\n // ── Auth ──────────────────────────────────────────────────────────\n // F4 Wave B: auth is now handled by the `#vt=` fragment in the iframe\n // src. The agent exchanges it for a scoped cookie on first load. No\n // postMessage handoff needed.\n\n const handleIframeLoad = useCallback((tabId: string) => {\n setConnectionStatus((prev) => ({ ...prev, [tabId]: 'connected' }));\n }, []);\n\n const handleIframeError = useCallback((tabId: string) => {\n setConnectionStatus((prev) => ({ ...prev, [tabId]: 'error' }));\n }, []);\n\n const handleReload = useCallback((tabId: string) => {\n const iframe = iframeRefs.current[tabId];\n if (iframe) {\n setConnectionStatus((prev) => ({ ...prev, [tabId]: 'connecting' }));\n const src = iframe.src;\n iframe.src = 'about:blank';\n requestAnimationFrame(() => {\n iframe.src = src;\n });\n }\n }, []);\n\n const handleOpenExternal = useCallback(\n (tabId: string) => {\n const tab = tabs.find((t) => t.id === tabId);\n if (!tab?.uiUrl) return;\n window.open(tab.uiUrl, '_blank');\n },\n [tabs]\n );\n\n // ── Tab keyboard navigation (roving tabindex) ─────────────────────\n\n const focusTab = useCallback((tabId: string) => {\n setActiveTabId(tabId);\n tabRefs.current[tabId]?.focus();\n }, []);\n\n const handleTabKeyDown = useCallback(\n (e: ReactKeyboardEvent<HTMLDivElement>, tabId: string) => {\n // Children (the close button) handle their own keys.\n if (e.target !== e.currentTarget) return;\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n setActiveTabId(tabId);\n return;\n }\n const idx = tabs.findIndex((t) => t.id === tabId);\n if (idx === -1) return;\n if (e.key === 'ArrowRight') {\n e.preventDefault();\n focusTab(tabs[(idx + 1) % tabs.length].id);\n } else if (e.key === 'ArrowLeft') {\n e.preventDefault();\n focusTab(tabs[(idx - 1 + tabs.length) % tabs.length].id);\n } else if (e.key === 'Home') {\n e.preventDefault();\n focusTab(tabs[0].id);\n } else if (e.key === 'End') {\n e.preventDefault();\n focusTab(tabs[tabs.length - 1].id);\n }\n },\n [tabs, focusTab]\n );\n\n // ── Drag-to-resize (pointer events; listeners only while dragging) ─\n\n const handleResizePointerDown = useCallback(\n (e: ReactPointerEvent<HTMLDivElement>) => {\n if (maximized) return;\n e.preventDefault();\n startYRef.current = e.clientY;\n startHeightRef.current = contentRef.current?.offsetHeight ?? 480;\n setIsResizing(true);\n },\n [maximized]\n );\n\n useEffect(() => {\n if (!isResizing) return;\n const handlePointerMove = (e: PointerEvent) => {\n const delta = startYRef.current - e.clientY;\n setResizeHeight(clampHeight(startHeightRef.current + delta));\n };\n const endResize = () => setIsResizing(false);\n document.addEventListener('pointermove', handlePointerMove);\n document.addEventListener('pointerup', endResize);\n document.addEventListener('pointercancel', endResize);\n document.body.style.cursor = 'ns-resize';\n document.body.style.userSelect = 'none';\n return () => {\n document.removeEventListener('pointermove', handlePointerMove);\n document.removeEventListener('pointerup', endResize);\n document.removeEventListener('pointercancel', endResize);\n document.body.style.cursor = '';\n document.body.style.userSelect = '';\n };\n }, [isResizing]);\n\n // Keyboard alternative for the resize separator.\n const handleResizeKeyDown = useCallback(\n (e: ReactKeyboardEvent<HTMLDivElement>) => {\n if (maximized) return;\n if (e.key !== 'ArrowUp' && e.key !== 'ArrowDown') return;\n e.preventDefault();\n const current = resizeHeight ?? contentRef.current?.offsetHeight ?? 480;\n const next = clampHeight(\n current + (e.key === 'ArrowUp' ? RESIZE_KEY_STEP : -RESIZE_KEY_STEP)\n );\n setResizeHeight(next);\n },\n [maximized, resizeHeight]\n );\n\n // Baseline measurement so the separator can report aria-valuenow before\n // the user has resized.\n useEffect(() => {\n setMeasuredHeight(contentRef.current?.offsetHeight ?? null);\n }, []);\n\n // ── Render helpers ────────────────────────────────────────────────\n\n const renderStatusDot = (tabId: string) => {\n const status = connectionStatus[tabId];\n if (status === 'connected')\n return <Wifi className=\"size-3 text-status-success-text shrink-0\" aria-hidden=\"true\" />;\n if (status === 'error')\n return <WifiOff className=\"size-3 text-status-error-text shrink-0\" aria-hidden=\"true\" />;\n if (status === 'connecting')\n return (\n <Loader2\n className=\"size-3 animate-spin text-status-warning-text shrink-0\"\n aria-hidden=\"true\"\n />\n );\n return <Monitor className=\"size-3 text-text-muted shrink-0\" aria-hidden=\"true\" />;\n };\n\n const panelHeightClass = maximized\n ? 'h-[80vh]'\n : resizeHeight\n ? undefined\n : 'h-[60vh] min-h-[20rem]';\n const panelStyle = !maximized && resizeHeight ? { height: `${resizeHeight}px` } : undefined;\n\n const tabDomId = (tabId: string) => `${tabId}-tab`;\n const panelDomId = (tabId: string) => `${tabId}-panel`;\n\n return (\n <div className=\"border border-border-default rounded-lg overflow-hidden bg-bg-sunken\">\n {/* Resize handle — pointer drag or ArrowUp/ArrowDown when focused */}\n <div\n role=\"separator\"\n tabIndex={0}\n aria-orientation=\"horizontal\"\n aria-label={tr('vibecontrols.pluginPanel.resize', 'Resize panel')}\n aria-valuenow={Math.round(resizeHeight ?? measuredHeight ?? 480)}\n aria-valuemin={MIN_PANEL_HEIGHT}\n aria-valuemax={typeof window !== 'undefined' ? window.innerHeight - 150 : 800}\n onPointerDown={handleResizePointerDown}\n onKeyDown={handleResizeKeyDown}\n className=\"group flex h-2 max-md:h-3 cursor-ns-resize touch-none items-center justify-center transition-colors hover:bg-bg-sunken focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus\"\n title={tr('vibecontrols.pluginPanel.resize', 'Resize panel')}\n >\n <GripVertical\n className=\"w-4 h-3 rotate-90 text-text-muted transition-opacity opacity-100 md:opacity-0 md:group-hover:opacity-100 md:group-focus-within:opacity-100\"\n aria-hidden=\"true\"\n />\n </div>\n\n {/* ── Tab Bar ────────────────────────────────────────────────── */}\n <div className=\"flex items-center bg-bg-sunken border-b border-border-default overflow-x-auto\">\n <div\n role=\"tablist\"\n aria-label={tr('vibecontrols.pluginPanel.tabs', 'Plugin tabs')}\n className=\"flex items-center\"\n >\n {tabs.map((tab) => {\n const isActive = tab.id === activeTab?.id;\n return (\n <div\n role=\"tab\"\n id={tabDomId(tab.id)}\n aria-selected={isActive}\n aria-controls={panelDomId(tab.id)}\n tabIndex={isActive ? 0 : -1}\n ref={(el) => {\n tabRefs.current[tab.id] = el;\n }}\n onKeyDown={(e) => handleTabKeyDown(e, tab.id)}\n key={tab.id}\n className={`group flex items-center gap-1.5 pl-3 pr-1.5 py-1.5 text-xs border-r border-border-subtle cursor-pointer transition-colors min-w-0 max-w-[200px] shrink-0 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-border-focus ${\n isActive\n ? 'bg-bg-surface text-text-primary border-b-2 border-b-action-primary-bg'\n : 'text-text-secondary hover:text-text-primary hover:bg-bg-surface/50'\n }`}\n onClick={() => setActiveTabId(tab.id)}\n >\n {renderStatusDot(tab.id)}\n <span className=\"truncate\">{tab.name}</span>\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation();\n handleCloseTab(tab.id);\n }}\n aria-label={tr('vibecontrols.pluginPanel.closeTab', 'Close tab {{name}}', {\n name: tab.name,\n })}\n className=\"ml-auto shrink-0 p-0.5 rounded-button transition-opacity hover:text-status-error-text focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus opacity-100 md:opacity-0 md:group-hover:opacity-100 md:group-focus-within:opacity-100\"\n >\n <X className=\"size-3\" aria-hidden=\"true\" />\n </button>\n </div>\n );\n })}\n </div>\n\n {/* Add tab button */}\n <button\n type=\"button\"\n onClick={handleAddTab}\n aria-label={tr('vibecontrols.pluginPanel.newTab', 'New tab')}\n className=\"flex items-center justify-center size-8 max-md:size-11 shrink-0 text-text-secondary hover:text-text-primary hover:bg-bg-surface/50 transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-border-focus\"\n >\n <Plus className=\"size-3.5\" aria-hidden=\"true\" />\n </button>\n\n {/* Spacer */}\n <div className=\"flex-1 min-w-0\" />\n\n {/* Global panel actions */}\n <div className=\"flex items-center gap-0.5 px-2 shrink-0\">\n {activeTab?.uiUrl && (\n <>\n <button\n type=\"button\"\n onClick={() => activeTab && handleReload(activeTab.id)}\n className={TOOLBAR_BTN}\n aria-label={tr('vibecontrols.pluginPanel.reload', 'Reload')}\n title={tr('vibecontrols.pluginPanel.reload', 'Reload')}\n >\n <RotateCcw className=\"size-3.5\" aria-hidden=\"true\" />\n </button>\n <button\n type=\"button\"\n onClick={() => activeTab && handleOpenExternal(activeTab.id)}\n className={TOOLBAR_BTN}\n aria-label={tr('vibecontrols.pluginPanel.openExternal', 'Open in new browser tab')}\n title={tr('vibecontrols.pluginPanel.openExternal', 'Open in new browser tab')}\n >\n <ExternalLink className=\"size-3.5\" aria-hidden=\"true\" />\n </button>\n </>\n )}\n <button\n type=\"button\"\n onClick={() => {\n setMaximized((prev) => !prev);\n setResizeHeight(null);\n }}\n className={TOOLBAR_BTN}\n aria-label={\n maximized\n ? tr('vibecontrols.pluginPanel.restore', 'Restore size')\n : tr('vibecontrols.pluginPanel.maximize', 'Maximize')\n }\n title={\n maximized\n ? tr('vibecontrols.pluginPanel.restore', 'Restore size')\n : tr('vibecontrols.pluginPanel.maximize', 'Maximize')\n }\n >\n {maximized ? (\n <Minimize2 className=\"size-3.5\" aria-hidden=\"true\" />\n ) : (\n <Maximize2 className=\"size-3.5\" aria-hidden=\"true\" />\n )}\n </button>\n </div>\n </div>\n\n {/* ── Address Bar (Plugin Selector Dropdown) ─────────────────── */}\n {activeTab && (\n <div className=\"flex items-center gap-2 px-3 py-1.5 bg-bg-surface border-b border-border-default\">\n <Monitor className=\"size-4 text-text-muted shrink-0\" aria-hidden=\"true\" />\n <div className=\"relative flex-1 min-w-0\">\n <DropdownMenu\n open={dropdownOpen === activeTab.id}\n onOpenChange={(open) => setDropdownOpen(open ? activeTab.id : null)}\n >\n <DropdownMenuTrigger asChild>\n <button\n type=\"button\"\n className=\"w-full flex items-center justify-between gap-2 px-3 py-1.5 text-base md:text-sm bg-bg-sunken border border-border-default rounded-md text-left hover:border-border-strong focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus transition-colors\"\n >\n <span\n className={`truncate ${activeTab.pluginName ? 'text-text-primary' : 'text-text-muted'}`}\n >\n {activeTab.pluginName\n ? `${activeTab.name} — ${activeTab.pluginName}`\n : 'Select a plugin to load...'}\n </span>\n <ChevronDown\n aria-hidden=\"true\"\n className={`size-3.5 text-text-muted shrink-0 transition-transform ${dropdownOpen === activeTab.id ? 'rotate-180' : ''}`}\n />\n </button>\n </DropdownMenuTrigger>\n <DropdownMenuContent\n align=\"start\"\n className=\"w-[var(--radix-dropdown-menu-trigger-width)] max-w-[calc(100vw-2rem)] max-h-64 overflow-y-auto\"\n >\n {availablePlugins.length > 0 ? (\n availablePlugins.map((opt) => {\n const isSelected = activeTab.pluginName === opt.pluginName;\n return (\n <DropdownMenuItem\n key={opt.pluginName}\n onSelect={() => void handleSelectPlugin(activeTab.id, opt.pluginName)}\n className={`cursor-pointer flex-col items-stretch gap-0.5 px-3 py-2.5 border-b border-border-subtle last:border-b-0 ${\n isSelected ? 'bg-action-primary-bg/10' : ''\n }`}\n >\n <div className=\"flex items-center justify-between gap-2\">\n <span className=\"text-sm font-medium text-text-primary\">\n {opt.displayName}\n </span>\n {opt.version && (\n <span className=\"text-xs text-text-muted\">\n {opt.version === 'latest' ? opt.version : `v${opt.version}`}\n </span>\n )}\n </div>\n {opt.description && (\n <p className=\"text-xs text-text-secondary\">{opt.description}</p>\n )}\n <p className=\"text-xs text-text-muted font-mono\">{opt.pluginName}</p>\n </DropdownMenuItem>\n );\n })\n ) : (\n <div className=\"px-3 py-4 text-center text-sm text-text-muted\">\n No UI plugins available\n </div>\n )}\n </DropdownMenuContent>\n </DropdownMenu>\n </div>\n </div>\n )}\n\n {/* ── Content Area ───────────────────────────────────────────── */}\n <div\n ref={contentRef}\n className={`${panelHeightClass || ''} min-h-[20rem] bg-bg-inverse text-text-inverse relative`}\n style={panelStyle}\n >\n {tabs.map((tab) => (\n <div\n key={tab.id}\n role=\"tabpanel\"\n id={panelDomId(tab.id)}\n aria-labelledby={tabDomId(tab.id)}\n className={`absolute inset-0 ${tab.id === activeTab?.id ? 'block' : 'hidden'}`}\n >\n {!tab.pluginName ? (\n // Empty state — no plugin selected\n <div className=\"flex items-center justify-center h-full\">\n <div className=\"text-center max-w-sm\">\n <Monitor\n className=\"size-10 text-text-secondary mx-auto mb-3\"\n aria-hidden=\"true\"\n />\n <p className=\"text-text-inverse text-sm font-medium\">No plugin selected</p>\n <p className=\"text-text-inverse/70 text-xs mt-1\">\n Use the dropdown above to pick a plugin UI to load in this tab.\n </p>\n <button\n type=\"button\"\n onClick={() => setDropdownOpen(tab.id)}\n className=\"mt-4 px-4 py-2 bg-bg-sunken text-text-primary text-sm rounded-md hover:bg-bg-muted transition-colors inline-flex items-center gap-2 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus\"\n >\n <ChevronDown className=\"size-3.5\" aria-hidden=\"true\" />\n Select Plugin\n </button>\n </div>\n </div>\n ) : !tab.uiUrl ? (\n // No tunnel\n <div className=\"flex items-center justify-center h-full\">\n <div className=\"text-center\">\n <AlertCircle\n className=\"size-8 text-text-secondary mx-auto mb-3\"\n aria-hidden=\"true\"\n />\n <p className=\"text-text-inverse text-sm\">Agent tunnel not active</p>\n <p className=\"text-text-inverse/70 text-xs mt-1\">\n Cannot load plugin UI without an active tunnel connection.\n </p>\n </div>\n </div>\n ) : (\n // Iframe\n <iframe\n ref={(el) => {\n iframeRefs.current[tab.id] = el;\n }}\n src={tab.uiUrl}\n title={`Plugin UI — ${tab.name}`}\n className=\"w-full h-full border-0\"\n allow=\"clipboard-read; clipboard-write\"\n onLoad={() => handleIframeLoad(tab.id)}\n onError={() => handleIframeError(tab.id)}\n />\n )}\n </div>\n ))}\n\n {/* Connection status overlay */}\n {activeTab && connectionStatus[activeTab.id] === 'connecting' && activeTab.uiUrl && (\n <div\n role=\"status\"\n className=\"absolute bottom-3 right-3 flex items-center gap-2 px-3 py-1.5 bg-bg-overlay backdrop-blur-sm rounded-md text-xs text-status-warning-border pointer-events-none\"\n >\n <Loader2 className=\"size-3 animate-spin\" aria-hidden=\"true\" />\n Connecting...\n </div>\n )}\n {activeTab && connectionStatus[activeTab.id] === 'error' && activeTab.uiUrl && (\n <div\n role=\"alert\"\n className=\"absolute bottom-3 right-3 flex items-center gap-2 px-3 py-1.5 bg-bg-overlay backdrop-blur-sm rounded-md text-xs text-status-error-border\"\n >\n <WifiOff className=\"size-3\" aria-hidden=\"true\" />\n Disconnected\n <button\n type=\"button\"\n onClick={() => handleReload(activeTab.id)}\n className=\"underline hover:text-status-error-border/80 transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus rounded-button\"\n >\n Reconnect\n </button>\n </div>\n )}\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"PluginUIPanel.js","names":[],"sources":["../../../src/components/agents/PluginUIPanel.tsx"],"sourcesContent":["import {\n useState,\n useCallback,\n useRef,\n useEffect,\n useId,\n type KeyboardEvent as ReactKeyboardEvent,\n type PointerEvent as ReactPointerEvent,\n} from 'react';\nimport { useMintAgentCapabilityMutation } from '@/generated/wspace-operations';\nimport { AgentCapabilityScope } from '@/generated/wspace-types';\nimport {\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuTrigger,\n} from '@burdenoff/fe-libs/ui';\nimport {\n X,\n Maximize2,\n Minimize2,\n ExternalLink,\n Loader2,\n AlertCircle,\n RotateCcw,\n Wifi,\n WifiOff,\n GripVertical,\n Plus,\n Monitor,\n ChevronDown,\n} from 'lucide-react';\nimport { useTr } from '../../shared/hooks/useTr';\n\n// ── Types ──────────────────────────────────────────────────────────────\n\nexport interface PluginUITab {\n /** Unique tab ID (allows duplicate plugins in multiple tabs) */\n id: string;\n /** The plugin name (e.g. \"ui-ai\") — may be null if nothing selected yet */\n pluginName: string | null;\n /** Display name shown on the tab */\n name: string;\n /** The full URL to load in the iframe */\n uiUrl: string | null;\n}\n\nexport interface UIPluginOption {\n /** The plugin name key (e.g. \"ui-ai\") */\n pluginName: string;\n /** Display name */\n displayName: string;\n /** Description shown in dropdown */\n description?: string;\n /** Version */\n version?: string;\n /** Custom URL to use instead of the default /ui/{pluginName} path (e.g. for code-server proxy) */\n customUrl?: string;\n}\n\ninterface PluginUIPanelProps {\n /** Available UI plugins the user can pick from */\n availablePlugins: UIPluginOption[];\n /** Base URL of the agent tunnel (e.g. \"https://xxx.trycloudflare.com\") */\n tunnelUrl: string | null;\n /**\n * Agent ID — F4 Wave B replaced the legacy `?apiKey=` URL embed +\n * postMessage handoff with `mintAgentCapability(IFRAME_PLUGIN)`. Each\n * plugin tab loads with `#vt=<token>` so the long-lived agent secret\n * never reaches the browser.\n */\n agentId: string | null;\n /** Canonical CLI profile name; required for capability mint scoping. */\n profile: string;\n}\n\nconst MIN_PANEL_HEIGHT = 320; // matches min-h-[20rem]\nconst RESIZE_KEY_STEP = 32;\n\nfunction clampHeight(height: number): number {\n const max = typeof window !== 'undefined' ? window.innerHeight - 150 : 800;\n return Math.max(MIN_PANEL_HEIGHT, Math.min(height, max));\n}\n\nconst TOOLBAR_BTN =\n 'inline-flex size-7 max-md:size-9 items-center justify-center rounded-button text-text-secondary transition-colors hover:bg-bg-surface/50 hover:text-text-primary focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus';\n\n/**\n * Embedded browser-like panel for plugin UIs.\n * Each tab has a dropdown to select which plugin to load. The same plugin can\n * be loaded in multiple tabs, each with its own navigation state.\n */\nexport function PluginUIPanel({\n availablePlugins,\n tunnelUrl,\n agentId,\n profile,\n}: PluginUIPanelProps) {\n const [mintAgentCapability] = useMintAgentCapabilityMutation();\n const tr = useTr();\n\n // Tab ids: useId prefix + per-instance counter — no module-global state.\n const idPrefix = useId();\n const tabCounterRef = useRef(0);\n const nextTabId = useCallback((): string => {\n tabCounterRef.current += 1;\n return `${idPrefix}-ui-tab-${tabCounterRef.current}`;\n }, [idPrefix]);\n\n const [tabs, setTabs] = useState<PluginUITab[]>(() => {\n // Start with one blank tab\n return [{ id: nextTabId(), pluginName: null, name: 'New Tab', uiUrl: null }];\n });\n const [activeTabId, setActiveTabId] = useState<string>(tabs[0].id);\n const [maximized, setMaximized] = useState(false);\n\n // Connection status per tab\n const [connectionStatus, setConnectionStatus] = useState<\n Record<string, 'connecting' | 'connected' | 'error'>\n >({});\n\n // Dropdown open state per tab\n const [dropdownOpen, setDropdownOpen] = useState<string | null>(null);\n\n // Drag-to-resize state\n const [resizeHeight, setResizeHeight] = useState<number | null>(null);\n const [isResizing, setIsResizing] = useState(false);\n const [measuredHeight, setMeasuredHeight] = useState<number | null>(null);\n const startYRef = useRef(0);\n const startHeightRef = useRef(0);\n const contentRef = useRef<HTMLDivElement>(null);\n\n // Iframe refs for reload; tab refs for roving-tabindex arrow navigation\n const iframeRefs = useRef<Record<string, HTMLIFrameElement | null>>({});\n const tabRefs = useRef<Record<string, HTMLDivElement | null>>({});\n\n const activeTab = tabs.find((t) => t.id === activeTabId) || tabs[0] || null;\n\n // Build UI URL for a plugin — F4 Wave B: append a short-lived\n // IFRAME_PLUGIN capability as `#vt=<token>` so the agent's `/ui/<name>`\n // bootstrap can exchange it for a scoped HttpOnly cookie. No\n // long-lived secret in the URL or postMessage.\n const buildUiUrl = useCallback(\n async (pluginName: string, customUrl?: string): Promise<string | null> => {\n if (!tunnelUrl || !agentId) return null;\n const base = tunnelUrl.replace(/\\/$/, '');\n\n // Use customUrl if provided (e.g. for code-server proxy path)\n const urlStr = customUrl\n ? customUrl.startsWith('http')\n ? customUrl\n : `${base}${customUrl}`\n : `${base}/ui/${pluginName}`;\n\n const finalUrl = new URL(urlStr).toString();\n\n try {\n const res = await mintAgentCapability({\n variables: {\n agentId,\n profile: profile ?? 'default',\n scope: AgentCapabilityScope.IframePlugin,\n // Pass the bare plugin name — the svc maps to `/ui/<plugin>`\n // which matches the agent's iframe-token allow-list. Previous\n // `ui:<plugin>` form produced an invalid `/ui/ui:<plugin>`\n // prefix that the agent now rejects (audit-B P0-SEC-03).\n resourceId: pluginName,\n ttlSeconds: 60,\n },\n });\n const token = res.data?.mintAgentCapability?.token;\n if (!token) return finalUrl;\n // code-server's reverse proxy reads the capability from the `?vt=`\n // QUERY (GET-only) and consumes it server-side to mint its session\n // cookie — it has no client-side fragment-exchange shim. `/ui/<plugin>`\n // (and `/terminal`) DO have that shim, so they keep the `#vt=`\n // fragment, which is never sent to proxies/logs/Referer.\n const builtUrl = new URL(finalUrl);\n const isCodeServer =\n pluginName === 'code-server' || builtUrl.pathname.startsWith('/code-server');\n if (isCodeServer) {\n builtUrl.searchParams.set('vt', token);\n return builtUrl.toString();\n }\n return `${finalUrl}#vt=${encodeURIComponent(token)}`;\n } catch {\n return finalUrl;\n }\n },\n [tunnelUrl, agentId, profile, mintAgentCapability]\n );\n\n // Display name for a plugin name\n const getDisplayName = useCallback(\n (pluginName: string): string => {\n const opt = availablePlugins.find((p) => p.pluginName === pluginName);\n return (\n opt?.displayName ||\n pluginName\n .replace(/^ui-/, '')\n .replace(/-/g, ' ')\n .replace(/\\b\\w/g, (c) => c.toUpperCase())\n );\n },\n [availablePlugins]\n );\n\n // ── Tab Actions ───────────────────────────────────────────────────\n\n const handleAddTab = useCallback(() => {\n const newTab: PluginUITab = {\n id: nextTabId(),\n pluginName: null,\n name: 'New Tab',\n uiUrl: null,\n };\n setTabs((prev) => [...prev, newTab]);\n setActiveTabId(newTab.id);\n setDropdownOpen(newTab.id);\n }, [nextTabId]);\n\n const handleCloseTab = useCallback(\n (tabId: string) => {\n setTabs((prev) => {\n const next = prev.filter((t) => t.id !== tabId);\n // If we closed the last tab, add a blank one\n if (next.length === 0) {\n const blank: PluginUITab = {\n id: nextTabId(),\n pluginName: null,\n name: 'New Tab',\n uiUrl: null,\n };\n next.push(blank);\n setActiveTabId(blank.id);\n } else if (activeTabId === tabId) {\n // Select the previous tab or the first one\n const closedIdx = prev.findIndex((t) => t.id === tabId);\n const newIdx = Math.max(0, closedIdx - 1);\n setActiveTabId(next[Math.min(newIdx, next.length - 1)].id);\n }\n return next;\n });\n // Clean up refs\n delete iframeRefs.current[tabId];\n delete tabRefs.current[tabId];\n setConnectionStatus((prev) => {\n const next = { ...prev };\n delete next[tabId];\n return next;\n });\n },\n [activeTabId, nextTabId]\n );\n\n const handleSelectPlugin = useCallback(\n async (tabId: string, pluginName: string) => {\n const opt = availablePlugins.find((p) => p.pluginName === pluginName);\n const displayName = getDisplayName(pluginName);\n setConnectionStatus((prev) => ({ ...prev, [tabId]: 'connecting' }));\n setDropdownOpen(null);\n const uiUrl = await buildUiUrl(pluginName, opt?.customUrl);\n setTabs((prev) =>\n prev.map((t) => (t.id === tabId ? { ...t, pluginName, name: displayName, uiUrl } : t))\n );\n },\n [availablePlugins, buildUiUrl, getDisplayName]\n );\n\n // ── Auth ──────────────────────────────────────────────────────────\n // F4 Wave B: auth is now handled by the `#vt=` fragment in the iframe\n // src. The agent exchanges it for a scoped cookie on first load. No\n // postMessage handoff needed.\n\n const handleIframeLoad = useCallback((tabId: string) => {\n setConnectionStatus((prev) => ({ ...prev, [tabId]: 'connected' }));\n }, []);\n\n const handleIframeError = useCallback((tabId: string) => {\n setConnectionStatus((prev) => ({ ...prev, [tabId]: 'error' }));\n }, []);\n\n const handleReload = useCallback((tabId: string) => {\n const iframe = iframeRefs.current[tabId];\n if (iframe) {\n setConnectionStatus((prev) => ({ ...prev, [tabId]: 'connecting' }));\n const src = iframe.src;\n iframe.src = 'about:blank';\n requestAnimationFrame(() => {\n iframe.src = src;\n });\n }\n }, []);\n\n const handleOpenExternal = useCallback(\n (tabId: string) => {\n const tab = tabs.find((t) => t.id === tabId);\n if (!tab?.uiUrl) return;\n window.open(tab.uiUrl, '_blank');\n },\n [tabs]\n );\n\n // ── Tab keyboard navigation (roving tabindex) ─────────────────────\n\n const focusTab = useCallback((tabId: string) => {\n setActiveTabId(tabId);\n tabRefs.current[tabId]?.focus();\n }, []);\n\n const handleTabKeyDown = useCallback(\n (e: ReactKeyboardEvent<HTMLDivElement>, tabId: string) => {\n // Children (the close button) handle their own keys.\n if (e.target !== e.currentTarget) return;\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n setActiveTabId(tabId);\n return;\n }\n const idx = tabs.findIndex((t) => t.id === tabId);\n if (idx === -1) return;\n if (e.key === 'ArrowRight') {\n e.preventDefault();\n focusTab(tabs[(idx + 1) % tabs.length].id);\n } else if (e.key === 'ArrowLeft') {\n e.preventDefault();\n focusTab(tabs[(idx - 1 + tabs.length) % tabs.length].id);\n } else if (e.key === 'Home') {\n e.preventDefault();\n focusTab(tabs[0].id);\n } else if (e.key === 'End') {\n e.preventDefault();\n focusTab(tabs[tabs.length - 1].id);\n }\n },\n [tabs, focusTab]\n );\n\n // ── Drag-to-resize (pointer events; listeners only while dragging) ─\n\n const handleResizePointerDown = useCallback(\n (e: ReactPointerEvent<HTMLDivElement>) => {\n if (maximized) return;\n e.preventDefault();\n startYRef.current = e.clientY;\n startHeightRef.current = contentRef.current?.offsetHeight ?? 480;\n setIsResizing(true);\n },\n [maximized]\n );\n\n useEffect(() => {\n if (!isResizing) return;\n const handlePointerMove = (e: PointerEvent) => {\n const delta = startYRef.current - e.clientY;\n setResizeHeight(clampHeight(startHeightRef.current + delta));\n };\n const endResize = () => setIsResizing(false);\n document.addEventListener('pointermove', handlePointerMove);\n document.addEventListener('pointerup', endResize);\n document.addEventListener('pointercancel', endResize);\n document.body.style.cursor = 'ns-resize';\n document.body.style.userSelect = 'none';\n return () => {\n document.removeEventListener('pointermove', handlePointerMove);\n document.removeEventListener('pointerup', endResize);\n document.removeEventListener('pointercancel', endResize);\n document.body.style.cursor = '';\n document.body.style.userSelect = '';\n };\n }, [isResizing]);\n\n // Keyboard alternative for the resize separator.\n const handleResizeKeyDown = useCallback(\n (e: ReactKeyboardEvent<HTMLDivElement>) => {\n if (maximized) return;\n if (e.key !== 'ArrowUp' && e.key !== 'ArrowDown') return;\n e.preventDefault();\n const current = resizeHeight ?? contentRef.current?.offsetHeight ?? 480;\n const next = clampHeight(\n current + (e.key === 'ArrowUp' ? RESIZE_KEY_STEP : -RESIZE_KEY_STEP)\n );\n setResizeHeight(next);\n },\n [maximized, resizeHeight]\n );\n\n // Baseline measurement so the separator can report aria-valuenow before\n // the user has resized.\n useEffect(() => {\n setMeasuredHeight(contentRef.current?.offsetHeight ?? null);\n }, []);\n\n // ── Render helpers ────────────────────────────────────────────────\n\n const renderStatusDot = (tabId: string) => {\n const status = connectionStatus[tabId];\n if (status === 'connected')\n return <Wifi className=\"size-3 text-status-success-text shrink-0\" aria-hidden=\"true\" />;\n if (status === 'error')\n return <WifiOff className=\"size-3 text-status-error-text shrink-0\" aria-hidden=\"true\" />;\n if (status === 'connecting')\n return (\n <Loader2\n className=\"size-3 animate-spin text-status-warning-text shrink-0\"\n aria-hidden=\"true\"\n />\n );\n return <Monitor className=\"size-3 text-text-muted shrink-0\" aria-hidden=\"true\" />;\n };\n\n const panelHeightClass = maximized\n ? 'h-[80vh]'\n : resizeHeight\n ? undefined\n : 'h-[60vh] min-h-[20rem]';\n const panelStyle = !maximized && resizeHeight ? { height: `${resizeHeight}px` } : undefined;\n\n const tabDomId = (tabId: string) => `${tabId}-tab`;\n const panelDomId = (tabId: string) => `${tabId}-panel`;\n\n return (\n <div className=\"border border-border-default rounded-lg overflow-hidden bg-bg-sunken\">\n {/* Resize handle — pointer drag or ArrowUp/ArrowDown when focused */}\n <div\n role=\"separator\"\n tabIndex={0}\n aria-orientation=\"horizontal\"\n aria-label={tr('vibecontrols.pluginPanel.resize', 'Resize panel')}\n aria-valuenow={Math.round(resizeHeight ?? measuredHeight ?? 480)}\n aria-valuemin={MIN_PANEL_HEIGHT}\n aria-valuemax={typeof window !== 'undefined' ? window.innerHeight - 150 : 800}\n onPointerDown={handleResizePointerDown}\n onKeyDown={handleResizeKeyDown}\n className=\"group flex h-2 max-md:h-3 cursor-ns-resize touch-none items-center justify-center transition-colors hover:bg-bg-sunken focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus\"\n title={tr('vibecontrols.pluginPanel.resize', 'Resize panel')}\n >\n <GripVertical\n className=\"w-4 h-3 rotate-90 text-text-muted transition-opacity opacity-100 md:opacity-0 md:group-hover:opacity-100 md:group-focus-within:opacity-100\"\n aria-hidden=\"true\"\n />\n </div>\n\n {/* ── Tab Bar ────────────────────────────────────────────────── */}\n <div className=\"flex items-center bg-bg-sunken border-b border-border-default overflow-x-auto\">\n <div\n role=\"tablist\"\n aria-label={tr('vibecontrols.pluginPanel.tabs', 'Plugin tabs')}\n className=\"flex items-center\"\n >\n {tabs.map((tab) => {\n const isActive = tab.id === activeTab?.id;\n return (\n <div\n role=\"tab\"\n id={tabDomId(tab.id)}\n aria-selected={isActive}\n aria-controls={panelDomId(tab.id)}\n tabIndex={isActive ? 0 : -1}\n ref={(el) => {\n tabRefs.current[tab.id] = el;\n }}\n onKeyDown={(e) => handleTabKeyDown(e, tab.id)}\n key={tab.id}\n className={`group flex items-center gap-1.5 pl-3 pr-1.5 py-1.5 text-xs border-r border-border-subtle cursor-pointer transition-colors min-w-0 max-w-[200px] shrink-0 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-border-focus ${\n isActive\n ? 'bg-bg-surface text-text-primary border-b-2 border-b-action-primary-bg'\n : 'text-text-secondary hover:text-text-primary hover:bg-bg-surface/50'\n }`}\n onClick={() => setActiveTabId(tab.id)}\n >\n {renderStatusDot(tab.id)}\n <span className=\"truncate\">{tab.name}</span>\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation();\n handleCloseTab(tab.id);\n }}\n aria-label={tr('vibecontrols.pluginPanel.closeTab', 'Close tab {{name}}', {\n name: tab.name,\n })}\n className=\"ml-auto shrink-0 p-0.5 rounded-button transition-opacity hover:text-status-error-text focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus opacity-100 md:opacity-0 md:group-hover:opacity-100 md:group-focus-within:opacity-100\"\n >\n <X className=\"size-3\" aria-hidden=\"true\" />\n </button>\n </div>\n );\n })}\n </div>\n\n {/* Add tab button */}\n <button\n type=\"button\"\n onClick={handleAddTab}\n aria-label={tr('vibecontrols.pluginPanel.newTab', 'New tab')}\n className=\"flex items-center justify-center size-8 max-md:size-11 shrink-0 text-text-secondary hover:text-text-primary hover:bg-bg-surface/50 transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-border-focus\"\n >\n <Plus className=\"size-3.5\" aria-hidden=\"true\" />\n </button>\n\n {/* Spacer */}\n <div className=\"flex-1 min-w-0\" />\n\n {/* Global panel actions */}\n <div className=\"flex items-center gap-0.5 px-2 shrink-0\">\n {activeTab?.uiUrl && (\n <>\n <button\n type=\"button\"\n onClick={() => activeTab && handleReload(activeTab.id)}\n className={TOOLBAR_BTN}\n aria-label={tr('vibecontrols.pluginPanel.reload', 'Reload')}\n title={tr('vibecontrols.pluginPanel.reload', 'Reload')}\n >\n <RotateCcw className=\"size-3.5\" aria-hidden=\"true\" />\n </button>\n <button\n type=\"button\"\n onClick={() => activeTab && handleOpenExternal(activeTab.id)}\n className={TOOLBAR_BTN}\n aria-label={tr('vibecontrols.pluginPanel.openExternal', 'Open in new browser tab')}\n title={tr('vibecontrols.pluginPanel.openExternal', 'Open in new browser tab')}\n >\n <ExternalLink className=\"size-3.5\" aria-hidden=\"true\" />\n </button>\n </>\n )}\n <button\n type=\"button\"\n onClick={() => {\n setMaximized((prev) => !prev);\n setResizeHeight(null);\n }}\n className={TOOLBAR_BTN}\n aria-label={\n maximized\n ? tr('vibecontrols.pluginPanel.restore', 'Restore size')\n : tr('vibecontrols.pluginPanel.maximize', 'Maximize')\n }\n title={\n maximized\n ? tr('vibecontrols.pluginPanel.restore', 'Restore size')\n : tr('vibecontrols.pluginPanel.maximize', 'Maximize')\n }\n >\n {maximized ? (\n <Minimize2 className=\"size-3.5\" aria-hidden=\"true\" />\n ) : (\n <Maximize2 className=\"size-3.5\" aria-hidden=\"true\" />\n )}\n </button>\n </div>\n </div>\n\n {/* ── Address Bar (Plugin Selector Dropdown) ─────────────────── */}\n {activeTab && (\n <div className=\"flex items-center gap-2 px-3 py-1.5 bg-bg-surface border-b border-border-default\">\n <Monitor className=\"size-4 text-text-muted shrink-0\" aria-hidden=\"true\" />\n <div className=\"relative flex-1 min-w-0\">\n <DropdownMenu\n open={dropdownOpen === activeTab.id}\n onOpenChange={(open) => setDropdownOpen(open ? activeTab.id : null)}\n >\n <DropdownMenuTrigger asChild>\n <button\n type=\"button\"\n className=\"w-full flex items-center justify-between gap-2 px-3 py-1.5 text-base md:text-sm bg-bg-sunken border border-border-default rounded-md text-left hover:border-border-strong focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus transition-colors\"\n >\n <span\n className={`truncate ${activeTab.pluginName ? 'text-text-primary' : 'text-text-muted'}`}\n >\n {activeTab.pluginName\n ? `${activeTab.name} — ${activeTab.pluginName}`\n : 'Select a plugin to load...'}\n </span>\n <ChevronDown\n aria-hidden=\"true\"\n className={`size-3.5 text-text-muted shrink-0 transition-transform ${dropdownOpen === activeTab.id ? 'rotate-180' : ''}`}\n />\n </button>\n </DropdownMenuTrigger>\n <DropdownMenuContent\n align=\"start\"\n className=\"w-[var(--radix-dropdown-menu-trigger-width)] max-w-[calc(100vw-2rem)] max-h-64 overflow-y-auto\"\n >\n {availablePlugins.length > 0 ? (\n availablePlugins.map((opt) => {\n const isSelected = activeTab.pluginName === opt.pluginName;\n return (\n <DropdownMenuItem\n key={opt.pluginName}\n onSelect={() => void handleSelectPlugin(activeTab.id, opt.pluginName)}\n className={`cursor-pointer flex-col items-stretch gap-0.5 px-3 py-2.5 border-b border-border-subtle last:border-b-0 ${\n isSelected ? 'bg-action-primary-bg/10' : ''\n }`}\n >\n <div className=\"flex items-center justify-between gap-2\">\n <span className=\"text-sm font-medium text-text-primary\">\n {opt.displayName}\n </span>\n {opt.version && (\n <span className=\"text-xs text-text-muted\">\n {opt.version === 'latest' ? opt.version : `v${opt.version}`}\n </span>\n )}\n </div>\n {opt.description && (\n <p className=\"text-xs text-text-secondary\">{opt.description}</p>\n )}\n <p className=\"text-xs text-text-muted font-mono\">{opt.pluginName}</p>\n </DropdownMenuItem>\n );\n })\n ) : (\n <div className=\"px-3 py-4 text-center text-sm text-text-muted\">\n No UI plugins available\n </div>\n )}\n </DropdownMenuContent>\n </DropdownMenu>\n </div>\n </div>\n )}\n\n {/* ── Content Area ───────────────────────────────────────────── */}\n <div\n ref={contentRef}\n className={`${panelHeightClass || ''} min-h-[20rem] bg-bg-inverse text-text-inverse relative`}\n style={panelStyle}\n >\n {tabs.map((tab) => (\n <div\n key={tab.id}\n role=\"tabpanel\"\n id={panelDomId(tab.id)}\n aria-labelledby={tabDomId(tab.id)}\n className={`absolute inset-0 ${tab.id === activeTab?.id ? 'block' : 'hidden'}`}\n >\n {!tab.pluginName ? (\n // Empty state — no plugin selected\n <div className=\"flex items-center justify-center h-full\">\n <div className=\"text-center max-w-sm\">\n <Monitor\n className=\"size-10 text-text-secondary mx-auto mb-3\"\n aria-hidden=\"true\"\n />\n <p className=\"text-text-inverse text-sm font-medium\">No plugin selected</p>\n <p className=\"text-text-inverse/70 text-xs mt-1\">\n Use the dropdown above to pick a plugin UI to load in this tab.\n </p>\n <button\n type=\"button\"\n onClick={() => setDropdownOpen(tab.id)}\n className=\"mt-4 px-4 py-2 bg-bg-sunken text-text-primary text-sm rounded-md hover:bg-bg-sunken transition-colors inline-flex items-center gap-2 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus\"\n >\n <ChevronDown className=\"size-3.5\" aria-hidden=\"true\" />\n Select Plugin\n </button>\n </div>\n </div>\n ) : !tab.uiUrl ? (\n // No tunnel\n <div className=\"flex items-center justify-center h-full\">\n <div className=\"text-center\">\n <AlertCircle\n className=\"size-8 text-text-secondary mx-auto mb-3\"\n aria-hidden=\"true\"\n />\n <p className=\"text-text-inverse text-sm\">Agent tunnel not active</p>\n <p className=\"text-text-inverse/70 text-xs mt-1\">\n Cannot load plugin UI without an active tunnel connection.\n </p>\n </div>\n </div>\n ) : (\n // Iframe\n <iframe\n ref={(el) => {\n iframeRefs.current[tab.id] = el;\n }}\n src={tab.uiUrl}\n title={`Plugin UI — ${tab.name}`}\n className=\"w-full h-full border-0\"\n allow=\"clipboard-read; clipboard-write\"\n onLoad={() => handleIframeLoad(tab.id)}\n onError={() => handleIframeError(tab.id)}\n />\n )}\n </div>\n ))}\n\n {/* Connection status overlay */}\n {activeTab && connectionStatus[activeTab.id] === 'connecting' && activeTab.uiUrl && (\n <div\n role=\"status\"\n className=\"absolute bottom-3 right-3 flex items-center gap-2 px-3 py-1.5 bg-bg-overlay backdrop-blur-sm rounded-md text-xs text-status-warning-border pointer-events-none\"\n >\n <Loader2 className=\"size-3 animate-spin\" aria-hidden=\"true\" />\n Connecting...\n </div>\n )}\n {activeTab && connectionStatus[activeTab.id] === 'error' && activeTab.uiUrl && (\n <div\n role=\"alert\"\n className=\"absolute bottom-3 right-3 flex items-center gap-2 px-3 py-1.5 bg-bg-overlay backdrop-blur-sm rounded-md text-xs text-status-error-border\"\n >\n <WifiOff className=\"size-3\" aria-hidden=\"true\" />\n Disconnected\n <button\n type=\"button\"\n onClick={() => handleReload(activeTab.id)}\n className=\"underline hover:text-status-error-border/80 transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus rounded-button\"\n >\n Reconnect\n </button>\n </div>\n )}\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -18,7 +18,7 @@ var c = {
|
|
|
18
18
|
EXPORT: "bg-status-warning-bg-subtle text-status-warning-text",
|
|
19
19
|
SHARE: "bg-status-success-bg-subtle text-status-success-text",
|
|
20
20
|
REVOKE: "bg-status-warning-bg-subtle text-status-warning-text",
|
|
21
|
-
ARCHIVE: "bg-bg-
|
|
21
|
+
ARCHIVE: "bg-bg-sunken text-text-secondary",
|
|
22
22
|
RESTORE: "bg-status-info-bg-subtle text-status-info-text"
|
|
23
23
|
};
|
|
24
24
|
function u(e) {
|