@burdenoff/microfe-vibecontrols 2026.625.2 → 2026.626.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/agents/AgentForm.js +129 -127
- package/dist/components/agents/AgentForm.js.map +1 -1
- package/dist/components/agents/AgentStatusBadge.js +11 -4
- package/dist/components/agents/AgentStatusBadge.js.map +1 -1
- package/dist/components/sessions/SessionStatusBadge.js +11 -8
- package/dist/components/sessions/SessionStatusBadge.js.map +1 -1
- package/dist/components/shared/EmptyState.js +14 -8
- package/dist/components/shared/EmptyState.js.map +1 -1
- package/dist/components/shared/InstallTabs.js +19 -7
- package/dist/components/shared/InstallTabs.js.map +1 -1
- package/dist/components/shared/PageHeader.js.map +1 -1
- package/dist/components/shared/PageSkeleton.js +4 -1
- package/dist/components/shared/PageSkeleton.js.map +1 -1
- package/dist/components/shared/SessionNotificationBanner.js +12 -5
- package/dist/components/shared/SessionNotificationBanner.js.map +1 -1
- package/dist/components/shared/StatusBadge.js +2 -2
- package/dist/components/shared/StatusBadge.js.map +1 -1
- package/dist/pages/ActionsPage.js +209 -204
- package/dist/pages/ActionsPage.js.map +1 -1
- package/dist/pages/AgentDetailsPage.js +678 -648
- package/dist/pages/AgentDetailsPage.js.map +1 -1
- package/dist/pages/AgentGraphPage.js +112 -108
- package/dist/pages/AgentGraphPage.js.map +1 -1
- package/dist/pages/AgentsPage.js +160 -155
- package/dist/pages/AgentsPage.js.map +1 -1
- package/dist/pages/AnalyticsPage.js +140 -136
- package/dist/pages/AnalyticsPage.js.map +1 -1
- package/dist/pages/CatalogDetailPage.js +249 -244
- package/dist/pages/CatalogDetailPage.js.map +1 -1
- package/dist/pages/CatalogPage.js +73 -68
- package/dist/pages/CatalogPage.js.map +1 -1
- package/dist/pages/ComponentDetailPage.js +195 -164
- package/dist/pages/ComponentDetailPage.js.map +1 -1
- package/dist/pages/ComponentsPage.js +80 -75
- package/dist/pages/ComponentsPage.js.map +1 -1
- package/dist/pages/EnvTopologyPage.js +100 -95
- package/dist/pages/EnvTopologyPage.js.map +1 -1
- package/dist/pages/HealthPage.js +331 -204
- package/dist/pages/HealthPage.js.map +1 -1
- package/dist/pages/LogsPage.js +54 -49
- package/dist/pages/LogsPage.js.map +1 -1
- package/dist/pages/OverviewPage.js +251 -241
- package/dist/pages/OverviewPage.js.map +1 -1
- package/dist/pages/PluginsCatalogPage.js +72 -78
- package/dist/pages/PluginsCatalogPage.js.map +1 -1
- package/dist/pages/SessionDetailsPage.js +517 -512
- package/dist/pages/SessionDetailsPage.js.map +1 -1
- package/dist/pages/SessionsPage.js +1084 -1078
- package/dist/pages/SessionsPage.js.map +1 -1
- package/dist/pages/SettingsPage.js +107 -103
- package/dist/pages/SettingsPage.js.map +1 -1
- package/dist/pages/TargetDetailsPage.js +131 -123
- package/dist/pages/TargetDetailsPage.js.map +1 -1
- package/dist/pages/TargetsPage.js +265 -260
- package/dist/pages/TargetsPage.js.map +1 -1
- package/dist/pages/TemplatesPage.js +77 -71
- package/dist/pages/TemplatesPage.js.map +1 -1
- package/dist/pages/VibeAuditPage.js +83 -78
- package/dist/pages/VibeAuditPage.js.map +1 -1
- package/dist/pages/VibeCalendarPage.js +109 -104
- package/dist/pages/VibeCalendarPage.js.map +1 -1
- package/dist/pages/VibeDeckPage.js +222 -217
- package/dist/pages/VibeDeckPage.js.map +1 -1
- package/dist/pages/VibeDetailsPage.js +532 -501
- package/dist/pages/VibeDetailsPage.js.map +1 -1
- package/dist/pages/VibesPage.js +138 -133
- package/dist/pages/VibesPage.js.map +1 -1
- package/dist/pages/WebhooksPage.js +329 -324
- package/dist/pages/WebhooksPage.js.map +1 -1
- package/dist/pages/ai/AIPage.js +149 -144
- package/dist/pages/ai/AIPage.js.map +1 -1
- package/package.json +2 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PageSkeleton.js","names":[],"sources":["../../../src/components/shared/PageSkeleton.tsx"],"sourcesContent":["import type { ReactNode } from 'react';\n\ninterface SkeletonLineProps {\n width?: string;\n height?: string;\n className?: string;\n}\n\nexport function SkeletonLine({\n width = 'w-full',\n height = 'h-4',\n className = '',\n}: SkeletonLineProps) {\n return <div
|
|
1
|
+
{"version":3,"file":"PageSkeleton.js","names":[],"sources":["../../../src/components/shared/PageSkeleton.tsx"],"sourcesContent":["import type { ReactNode } from 'react';\n\ninterface SkeletonLineProps {\n width?: string;\n height?: string;\n className?: string;\n}\n\nexport function SkeletonLine({\n width = 'w-full',\n height = 'h-4',\n className = '',\n}: SkeletonLineProps) {\n // Boff UI 2.0 — a real glass shimmer sweep instead of a flat opacity pulse.\n // The inner overlay sweeps left→right via `animate-vc-shimmer`; under reduced\n // motion the sweep is disabled (motion-safe) and the calm sunken fill remains.\n return (\n <div\n className={`relative overflow-hidden rounded bg-bg-sunken ${width} ${height} ${className}`}\n >\n <div className=\"absolute inset-0 -translate-x-full bg-[linear-gradient(90deg,transparent,color-mix(in_oklab,var(--color-text-primary)_8%,transparent),transparent)] motion-safe:animate-vc-shimmer\" />\n </div>\n );\n}\n\ninterface SkeletonRowsProps {\n count?: number;\n rowClassName?: string;\n}\n\nexport function SkeletonRows({ count = 6, rowClassName = '' }: SkeletonRowsProps) {\n return (\n <div className=\"space-y-3\" aria-hidden=\"true\">\n {Array.from({ length: count }).map((_, i) => (\n <div\n key={i}\n className={`flex items-center gap-3 rounded-card border border-border-subtle bg-bg-surface shadow-elevation-1 p-4 ${rowClassName}`}\n >\n <SkeletonLine width=\"w-8\" height=\"h-8\" className=\"rounded-full\" />\n <div className=\"flex-1 space-y-2\">\n <SkeletonLine width=\"w-1/3\" />\n <SkeletonLine width=\"w-1/2\" height=\"h-3\" />\n </div>\n <SkeletonLine width=\"w-16\" height=\"h-6\" />\n </div>\n ))}\n </div>\n );\n}\n\ninterface SkeletonCardsProps {\n count?: number;\n columns?: number;\n}\n\nexport function SkeletonCards({ count = 6, columns = 3 }: SkeletonCardsProps) {\n const gridCols =\n columns === 4\n ? 'sm:grid-cols-2 lg:grid-cols-4'\n : columns === 2\n ? 'sm:grid-cols-2'\n : 'sm:grid-cols-2 lg:grid-cols-3';\n return (\n <div className={`grid grid-cols-1 gap-4 ${gridCols}`} aria-hidden=\"true\">\n {Array.from({ length: count }).map((_, i) => (\n <div\n key={i}\n className=\"rounded-card border border-border-subtle bg-bg-surface shadow-elevation-1 p-5 space-y-3\"\n >\n <SkeletonLine width=\"w-2/3\" height=\"h-5\" />\n <SkeletonLine width=\"w-full\" height=\"h-3\" />\n <SkeletonLine width=\"w-1/2\" height=\"h-3\" />\n </div>\n ))}\n </div>\n );\n}\n\ninterface PageSkeletonProps {\n header?: ReactNode;\n variant?: 'rows' | 'cards' | 'detail';\n count?: number;\n}\n\nexport function PageSkeleton({ header, variant = 'rows', count = 6 }: PageSkeletonProps) {\n return (\n <div className=\"space-y-6\" role=\"status\" aria-label=\"Loading content\">\n {header}\n {variant === 'cards' ? (\n <SkeletonCards count={count} />\n ) : variant === 'detail' ? (\n <div className=\"space-y-3\">\n <SkeletonLine width=\"w-1/3\" height=\"h-6\" />\n <SkeletonLine width=\"w-2/3\" height=\"h-3\" />\n <SkeletonLine width=\"w-full\" height=\"h-3\" />\n <SkeletonLine width=\"w-3/4\" height=\"h-3\" />\n <SkeletonRows count={Math.max(count - 2, 3)} />\n </div>\n ) : (\n <SkeletonRows count={count} />\n )}\n </div>\n );\n}\n"],"mappings":";;AAQA,SAAgB,EAAa,EAC3B,WAAQ,UACR,YAAS,OACT,eAAY,MACQ;AAIpB,QACE,kBAAC,OAAD;EACE,WAAW,iDAAiD,EAAM,GAAG,EAAO,GAAG;YAE/E,kBAAC,OAAD,EAAK,WAAU,sLAAuL,CAAA;EAClM,CAAA;;AASV,SAAgB,EAAa,EAAE,WAAQ,GAAG,kBAAe,MAAyB;AAChF,QACE,kBAAC,OAAD;EAAK,WAAU;EAAY,eAAY;YACpC,MAAM,KAAK,EAAE,QAAQ,GAAO,CAAC,CAAC,KAAK,GAAG,MACrC,kBAAC,OAAD;GAEE,WAAW,yGAAyG;aAFtH;IAIE,kBAAC,GAAD;KAAc,OAAM;KAAM,QAAO;KAAM,WAAU;KAAiB,CAAA;IAClE,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,GAAD,EAAc,OAAM,SAAU,CAAA,EAC9B,kBAAC,GAAD;MAAc,OAAM;MAAQ,QAAO;MAAQ,CAAA,CACvC;;IACN,kBAAC,GAAD;KAAc,OAAM;KAAO,QAAO;KAAQ,CAAA;IACtC;KATC,EASD,CACN;EACE,CAAA;;AASV,SAAgB,EAAc,EAAE,WAAQ,GAAG,aAAU,KAAyB;AAO5E,QACE,kBAAC,OAAD;EAAK,WAAW,0BANhB,MAAY,IACR,kCACA,MAAY,IACV,mBACA;EAEgD,eAAY;YAC/D,MAAM,KAAK,EAAE,QAAQ,GAAO,CAAC,CAAC,KAAK,GAAG,MACrC,kBAAC,OAAD;GAEE,WAAU;aAFZ;IAIE,kBAAC,GAAD;KAAc,OAAM;KAAQ,QAAO;KAAQ,CAAA;IAC3C,kBAAC,GAAD;KAAc,OAAM;KAAS,QAAO;KAAQ,CAAA;IAC5C,kBAAC,GAAD;KAAc,OAAM;KAAQ,QAAO;KAAQ,CAAA;IACvC;KANC,EAMD,CACN;EACE,CAAA;;AAUV,SAAgB,EAAa,EAAE,WAAQ,aAAU,QAAQ,WAAQ,KAAwB;AACvF,QACE,kBAAC,OAAD;EAAK,WAAU;EAAY,MAAK;EAAS,cAAW;YAApD,CACG,GACA,MAAY,UACX,kBAAC,GAAD,EAAsB,UAAS,CAAA,GAC7B,MAAY,WACd,kBAAC,OAAD;GAAK,WAAU;aAAf;IACE,kBAAC,GAAD;KAAc,OAAM;KAAQ,QAAO;KAAQ,CAAA;IAC3C,kBAAC,GAAD;KAAc,OAAM;KAAQ,QAAO;KAAQ,CAAA;IAC3C,kBAAC,GAAD;KAAc,OAAM;KAAS,QAAO;KAAQ,CAAA;IAC5C,kBAAC,GAAD;KAAc,OAAM;KAAQ,QAAO;KAAQ,CAAA;IAC3C,kBAAC,GAAD,EAAc,OAAO,KAAK,IAAI,IAAQ,GAAG,EAAE,EAAI,CAAA;IAC3C;OAEN,kBAAC,GAAD,EAAqB,UAAS,CAAA,CAE5B"}
|
|
@@ -9,11 +9,18 @@ function y({ status: t, onReconnect: n }) {
|
|
|
9
9
|
return t === "disconnected" ? null : /* @__PURE__ */ v("div", {
|
|
10
10
|
className: "flex items-center gap-2 px-3 py-1.5 rounded-lg bg-bg-secondary border border-border-primary text-sm",
|
|
11
11
|
children: [
|
|
12
|
-
t === "connected" && /* @__PURE__ */ v(g, { children: [
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
12
|
+
t === "connected" && /* @__PURE__ */ v(g, { children: [
|
|
13
|
+
/* @__PURE__ */ _("span", {
|
|
14
|
+
"aria-hidden": "true",
|
|
15
|
+
className: "size-2 rounded-full bg-status-success-text motion-safe:animate-vc-glow-pulse"
|
|
16
|
+
}),
|
|
17
|
+
/* @__PURE__ */ _(f, { className: "size-4 text-status-success-text" }),
|
|
18
|
+
/* @__PURE__ */ _("span", {
|
|
19
|
+
className: "text-status-success-text font-medium",
|
|
20
|
+
children: r("vibecontrols.shared.sessionNotificationBanner.live", "Live")
|
|
21
|
+
})
|
|
22
|
+
] }),
|
|
23
|
+
t === "connecting" && /* @__PURE__ */ v(g, { children: [/* @__PURE__ */ _(f, { className: "size-4 text-status-warning-text motion-safe:animate-vc-glow-pulse" }), /* @__PURE__ */ _("span", {
|
|
17
24
|
className: "text-status-warning-text",
|
|
18
25
|
children: r("vibecontrols.shared.sessionNotificationBanner.connecting", "Connecting...")
|
|
19
26
|
})] }),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SessionNotificationBanner.js","names":[],"sources":["../../../src/components/shared/SessionNotificationBanner.tsx"],"sourcesContent":["/**\n * SessionNotificationBanner\n *\n * Displays AI tool notifications contextually within the Sessions page,\n * positioned above the session list for immediate visibility.\n *\n * Features:\n * - Source-specific styling (OpenCode, Claude Code, Gemini, Cursor)\n * - Event-type icons (success, error, info)\n * - Separate notification state from global toasts (no auto-dismiss interference)\n * - Manual dismiss button\n * - Smooth enter/exit animations\n *\n * Architecture:\n * - SessionNotifications maintains its OWN notification state separate from the global toast system\n * - It subscribes to the same WebSocket events but stores notifications independently\n * - This allows global toasts to auto-dismiss without affecting session banners\n */\nimport { useEffect, useState, useCallback, useRef } from 'react';\nimport {\n Bell,\n CheckCircle,\n AlertCircle,\n Info,\n X,\n Sparkles,\n Terminal,\n Zap,\n Wifi,\n WifiOff,\n} from 'lucide-react';\n\nimport type { AiToolNotification, ConnectionStatus } from '../../hooks/useAgentEvents';\nimport { useAgentEventsContext } from '../../providers/AgentEventsProvider';\nimport { useTr } from '../../shared/hooks/useTr';\n\n/**\n * Props for the connection status indicator\n */\ninterface ConnectionStatusIndicatorProps {\n status: ConnectionStatus;\n onReconnect: () => void;\n}\n\n/**\n * Connection status indicator showing WebSocket state\n */\nexport function ConnectionStatusIndicator({ status, onReconnect }: ConnectionStatusIndicatorProps) {\n const tr = useTr();\n\n if (status === 'disconnected') {\n return null;\n }\n\n return (\n <div className=\"flex items-center gap-2 px-3 py-1.5 rounded-lg bg-bg-secondary border border-border-primary text-sm\">\n {status === 'connected' && (\n <>\n <Wifi className=\"size-4 text-status-success-text\" />\n <span className=\"text-status-success-text font-medium\">\n {tr('vibecontrols.shared.sessionNotificationBanner.live', 'Live')}\n </span>\n </>\n )}\n {status === 'connecting' && (\n <>\n <Wifi className=\"size-4 text-status-warning-text animate-pulse\" />\n <span className=\"text-status-warning-text\">\n {tr('vibecontrols.shared.sessionNotificationBanner.connecting', 'Connecting...')}\n </span>\n </>\n )}\n {status === 'error' && (\n <>\n <WifiOff className=\"size-4 text-status-error-text\" />\n <span className=\"text-status-error-text\">\n {tr('vibecontrols.shared.sessionNotificationBanner.disconnected', 'Disconnected')}\n </span>\n <button\n type=\"button\"\n onClick={onReconnect}\n className=\"ml-2 px-2 py-0.5 text-xs rounded bg-accent-primary/10 text-accent-primary hover:bg-accent-primary/20 transition-colors\"\n >\n {tr('vibecontrols.shared.sessionNotificationBanner.reconnect', 'Reconnect')}\n </button>\n </>\n )}\n </div>\n );\n}\n\n/**\n * Props for a single notification banner\n */\ninterface NotificationBannerProps {\n notification: AiToolNotification;\n onDismiss: (id: string) => void;\n autoDismiss?: boolean;\n autoDismissDelay?: number;\n}\n\n/**\n * Get the icon for a source tool\n */\nfunction getSourceIcon(source: string) {\n const iconClass = 'size-5';\n\n switch (source.toLowerCase()) {\n case 'opencode':\n return <Terminal className={iconClass} />;\n case 'claude-code':\n case 'claudecode':\n return <Sparkles className={iconClass} />;\n case 'gemini-cli':\n case 'gemini':\n return <Zap className={iconClass} />;\n case 'cursor':\n return <Terminal className={iconClass} />;\n default:\n return <Bell className={iconClass} />;\n }\n}\n\n/**\n * Get the icon for an event type\n */\nfunction getEventIcon(eventType: string) {\n const iconClass = 'size-4';\n\n if (\n eventType.includes('completed') ||\n eventType.includes('success') ||\n eventType.includes('idle')\n ) {\n return <CheckCircle className={`${iconClass} text-status-success-text`} />;\n }\n if (eventType.includes('failed') || eventType.includes('error')) {\n return <AlertCircle className={`${iconClass} text-status-error-text`} />;\n }\n if (eventType.includes('question') || eventType.includes('permission')) {\n return <AlertCircle className={`${iconClass} text-status-warning-text`} />;\n }\n return <Info className={`${iconClass} text-status-info-text`} />;\n}\n\n/**\n * Get display name for a source tool\n */\nfunction getSourceDisplayName(source: string): string {\n const names: Record<string, string> = {\n opencode: 'OpenCode',\n 'opencode-vibe-webhook': 'OpenCode',\n 'claude-code': 'Claude Code',\n claudecode: 'Claude Code',\n 'gemini-cli': 'Gemini CLI',\n gemini: 'Gemini CLI',\n cursor: 'Cursor',\n };\n return names[source.toLowerCase()] || source;\n}\n\n/**\n * Get background color based on source\n */\nfunction getSourceStyles(source: string): { bg: string; border: string; text: string } {\n switch (source.toLowerCase()) {\n case 'opencode':\n case 'opencode-vibe-webhook':\n return {\n bg: 'bg-status-info-bg/10',\n border: 'border-status-info-border',\n text: 'text-status-info-text',\n };\n case 'claude-code':\n case 'claudecode':\n return {\n bg: 'bg-status-warning-bg/10',\n border: 'border-status-warning-border',\n text: 'text-status-warning-text',\n };\n case 'gemini-cli':\n case 'gemini':\n return {\n bg: 'bg-status-info-bg/10',\n border: 'border-status-info-border',\n text: 'text-status-info-text',\n };\n case 'cursor':\n return {\n bg: 'bg-status-success-bg/10',\n border: 'border-status-success-border',\n text: 'text-status-success-text',\n };\n default:\n return {\n bg: 'bg-bg-secondary',\n border: 'border-border-primary',\n text: 'text-text-secondary',\n };\n }\n}\n\n/**\n * Format timestamp for display\n */\nfunction formatTimestamp(timestamp: string): string {\n try {\n const date = new Date(timestamp);\n return date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });\n } catch {\n return '';\n }\n}\n\n/**\n * Single notification banner\n */\nfunction NotificationBanner({\n notification,\n onDismiss,\n autoDismiss = true,\n autoDismissDelay = 8000,\n}: NotificationBannerProps) {\n const [isExiting, setIsExiting] = useState(false);\n const [progress, setProgress] = useState(100);\n const styles = getSourceStyles(notification.source);\n\n useEffect(() => {\n if (!autoDismiss) return;\n\n // Progress countdown\n const startTime = Date.now();\n const interval = setInterval(() => {\n const elapsed = Date.now() - startTime;\n const remaining = Math.max(0, 100 - (elapsed / autoDismissDelay) * 100);\n setProgress(remaining);\n }, 50);\n\n // Auto-dismiss timer\n const timer = setTimeout(() => {\n setIsExiting(true);\n setTimeout(() => onDismiss(notification.id), 300);\n }, autoDismissDelay);\n\n return () => {\n clearInterval(interval);\n clearTimeout(timer);\n };\n }, [autoDismiss, autoDismissDelay, notification.id, onDismiss]);\n\n const handleDismiss = () => {\n setIsExiting(true);\n setTimeout(() => onDismiss(notification.id), 300);\n };\n\n return (\n <div\n className={`\n relative flex items-start gap-3 p-4 rounded-lg border shadow-lg backdrop-blur-sm\n ${styles.bg} ${styles.border}\n transform transition-all duration-300 ease-out\n ${isExiting ? 'opacity-0 -translate-y-2 scale-95' : 'opacity-100 translate-y-0 scale-100'}\n animate-slide-in-down\n `}\n role=\"alert\"\n data-testid=\"session-notification-banner\"\n >\n {/* Source icon */}\n <div className={`flex-shrink-0 p-2 rounded-lg bg-bg-tertiary/50 ${styles.text}`}>\n {getSourceIcon(notification.source)}\n </div>\n\n {/* Content */}\n <div className=\"flex-1 min-w-0\">\n {/* Header */}\n <div className=\"flex items-center gap-2 mb-1\">\n <span className={`text-xs font-semibold uppercase tracking-wide ${styles.text}`}>\n {getSourceDisplayName(notification.source)}\n </span>\n {getEventIcon(notification.eventType)}\n <span className=\"text-xs text-text-muted\">{formatTimestamp(notification.timestamp)}</span>\n </div>\n\n {/* Title */}\n <p className=\"text-sm font-medium text-text-primary\">{notification.title}</p>\n\n {/* Message */}\n <p className=\"text-xs text-text-secondary mt-0.5\">{notification.message}</p>\n </div>\n\n {/* Dismiss button */}\n <button\n type=\"button\"\n onClick={handleDismiss}\n className=\"flex-shrink-0 p-1 rounded-md hover:bg-bg-tertiary/50 text-text-muted hover:text-text-secondary transition-colors\"\n aria-label=\"Dismiss notification\"\n >\n <X className=\"size-4\" />\n </button>\n\n {/* Progress bar for auto-dismiss */}\n {autoDismiss && (\n <div className=\"absolute bottom-0 left-0 right-0 h-0.5 bg-bg-tertiary/30 overflow-hidden rounded-b-lg\">\n <div\n className={`h-full ${styles.text.replace('text-', 'bg-')}/50 transition-all duration-50 ease-linear`}\n style={{ width: `${progress}%` }}\n />\n </div>\n )}\n </div>\n );\n}\n\n/**\n * Props for the session notifications container\n */\nexport interface SessionNotificationsProps {\n /**\n * Maximum number of notifications to display\n */\n maxVisible?: number;\n\n /**\n * Whether notifications should auto-dismiss (default: false - requires manual close)\n */\n autoDismiss?: boolean;\n\n /**\n * Auto-dismiss delay in milliseconds (only used if autoDismiss is true)\n */\n autoDismissDelay?: number;\n\n /**\n * Whether to show the connection status indicator\n */\n showConnectionStatus?: boolean;\n\n /**\n * Compact mode: drop the standalone \"Agent Notifications\" header row and\n * render nothing at all when there are no notifications, so the banner\n * stops reserving vertical space. The connection status is expected to be\n * rendered in the page header instead (see `SessionConnectionStatus`).\n */\n compact?: boolean;\n}\n\n/**\n * Standalone agent connection-status chip for placement in a page header.\n * Reclaims the row the notification banner used to spend on a dedicated\n * \"Agent Notifications\" + status line. Renders nothing when no agent is\n * configured for the current context.\n */\nexport function SessionConnectionStatus() {\n const { connectionStatus, reconnect, agentId } = useAgentEventsContext();\n if (!agentId) return null;\n return <ConnectionStatusIndicator status={connectionStatus} onReconnect={reconnect} />;\n}\n\n/**\n * Maximum notifications to keep in session banner state\n */\nconst MAX_SESSION_NOTIFICATIONS = 50;\n\n/**\n * Container component for session-contextual notifications\n * Displays notifications above the session list with connection status\n *\n * IMPORTANT: This component maintains its OWN notification state separate from the global\n * toast system. It subscribes to the same WebSocket events via the context but stores\n * notifications independently. This allows:\n * - Global toasts to auto-dismiss without affecting session banners\n * - Session banners to persist until manually dismissed by the user\n */\nexport function SessionNotifications({\n maxVisible = 3,\n autoDismiss = false,\n autoDismissDelay = 8000,\n showConnectionStatus = true,\n compact = false,\n}: SessionNotificationsProps) {\n const tr = useTr();\n const {\n notifications: globalNotifications,\n connectionStatus,\n reconnect,\n agentId,\n } = useAgentEventsContext();\n\n // Maintain separate notification state for session banners\n const [sessionNotifications, setSessionNotifications] = useState<AiToolNotification[]>([]);\n const seenIdsRef = useRef<Set<string>>(new Set());\n\n // Sync new notifications from global state to session state\n // We only ADD new notifications, never remove them (except via manual dismiss)\n useEffect(() => {\n const newNotifications: AiToolNotification[] = [];\n\n for (const notification of globalNotifications) {\n if (!seenIdsRef.current.has(notification.id)) {\n seenIdsRef.current.add(notification.id);\n newNotifications.push(notification);\n }\n }\n\n if (newNotifications.length > 0) {\n setSessionNotifications((prev) => {\n const updated = [...newNotifications, ...prev];\n return updated.slice(0, MAX_SESSION_NOTIFICATIONS);\n });\n }\n }, [globalNotifications]);\n\n // Handle manual dismiss - only affects session banner state\n const handleDismiss = useCallback((id: string) => {\n setSessionNotifications((prev) => prev.filter((n) => n.id !== id));\n }, []);\n\n const visibleNotifications = sessionNotifications.slice(0, maxVisible);\n const hiddenCount = Math.max(0, sessionNotifications.length - maxVisible);\n\n // Don't render anything if no agent is configured. In compact mode we also\n // render nothing while there are no notifications so the banner stops\n // reserving a wasted row (the connection status lives in the page header).\n if (!agentId && sessionNotifications.length === 0) {\n return null;\n }\n if (compact && sessionNotifications.length === 0) {\n return null;\n }\n\n return (\n <div\n className={compact ? 'space-y-2' : 'mb-4 space-y-3'}\n data-testid=\"session-notifications-container\"\n >\n {/* Connection status indicator — header row only in full mode. In\n compact mode the status is rendered in the page header instead. */}\n {!compact && showConnectionStatus && agentId && (\n <div className=\"flex items-center justify-between\">\n <div className=\"text-xs text-text-muted\">\n {tr(\n 'vibecontrols.shared.sessionNotificationBanner.agentNotifications',\n 'Agent Notifications'\n )}\n </div>\n <ConnectionStatusIndicator status={connectionStatus} onReconnect={reconnect} />\n </div>\n )}\n\n {/* Notification banners */}\n <div className=\"space-y-2\">\n {visibleNotifications.map((notification) => (\n <NotificationBanner\n key={notification.id}\n notification={notification}\n onDismiss={handleDismiss}\n autoDismiss={autoDismiss}\n autoDismissDelay={autoDismissDelay}\n />\n ))}\n </div>\n\n {/* Hidden count indicator */}\n {hiddenCount > 0 && (\n <div className=\"text-xs text-text-muted text-center py-1\">\n +{hiddenCount}{' '}\n {tr(\n 'vibecontrols.shared.sessionNotificationBanner.moreNotifications',\n 'more notification(s)'\n )}\n </div>\n )}\n </div>\n );\n}\n\nexport default SessionNotifications;\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"SessionNotificationBanner.js","names":[],"sources":["../../../src/components/shared/SessionNotificationBanner.tsx"],"sourcesContent":["/**\n * SessionNotificationBanner\n *\n * Displays AI tool notifications contextually within the Sessions page,\n * positioned above the session list for immediate visibility.\n *\n * Features:\n * - Source-specific styling (OpenCode, Claude Code, Gemini, Cursor)\n * - Event-type icons (success, error, info)\n * - Separate notification state from global toasts (no auto-dismiss interference)\n * - Manual dismiss button\n * - Smooth enter/exit animations\n *\n * Architecture:\n * - SessionNotifications maintains its OWN notification state separate from the global toast system\n * - It subscribes to the same WebSocket events but stores notifications independently\n * - This allows global toasts to auto-dismiss without affecting session banners\n */\nimport { useEffect, useState, useCallback, useRef } from 'react';\nimport {\n Bell,\n CheckCircle,\n AlertCircle,\n Info,\n X,\n Sparkles,\n Terminal,\n Zap,\n Wifi,\n WifiOff,\n} from 'lucide-react';\n\nimport type { AiToolNotification, ConnectionStatus } from '../../hooks/useAgentEvents';\nimport { useAgentEventsContext } from '../../providers/AgentEventsProvider';\nimport { useTr } from '../../shared/hooks/useTr';\n\n/**\n * Props for the connection status indicator\n */\ninterface ConnectionStatusIndicatorProps {\n status: ConnectionStatus;\n onReconnect: () => void;\n}\n\n/**\n * Connection status indicator showing WebSocket state\n */\nexport function ConnectionStatusIndicator({ status, onReconnect }: ConnectionStatusIndicatorProps) {\n const tr = useTr();\n\n if (status === 'disconnected') {\n return null;\n }\n\n return (\n <div className=\"flex items-center gap-2 px-3 py-1.5 rounded-lg bg-bg-secondary border border-border-primary text-sm\">\n {status === 'connected' && (\n <>\n <span\n aria-hidden=\"true\"\n className=\"size-2 rounded-full bg-status-success-text motion-safe:animate-vc-glow-pulse\"\n />\n <Wifi className=\"size-4 text-status-success-text\" />\n <span className=\"text-status-success-text font-medium\">\n {tr('vibecontrols.shared.sessionNotificationBanner.live', 'Live')}\n </span>\n </>\n )}\n {status === 'connecting' && (\n <>\n <Wifi className=\"size-4 text-status-warning-text motion-safe:animate-vc-glow-pulse\" />\n <span className=\"text-status-warning-text\">\n {tr('vibecontrols.shared.sessionNotificationBanner.connecting', 'Connecting...')}\n </span>\n </>\n )}\n {status === 'error' && (\n <>\n <WifiOff className=\"size-4 text-status-error-text\" />\n <span className=\"text-status-error-text\">\n {tr('vibecontrols.shared.sessionNotificationBanner.disconnected', 'Disconnected')}\n </span>\n <button\n type=\"button\"\n onClick={onReconnect}\n className=\"ml-2 px-2 py-0.5 text-xs rounded bg-accent-primary/10 text-accent-primary hover:bg-accent-primary/20 transition-colors\"\n >\n {tr('vibecontrols.shared.sessionNotificationBanner.reconnect', 'Reconnect')}\n </button>\n </>\n )}\n </div>\n );\n}\n\n/**\n * Props for a single notification banner\n */\ninterface NotificationBannerProps {\n notification: AiToolNotification;\n onDismiss: (id: string) => void;\n autoDismiss?: boolean;\n autoDismissDelay?: number;\n}\n\n/**\n * Get the icon for a source tool\n */\nfunction getSourceIcon(source: string) {\n const iconClass = 'size-5';\n\n switch (source.toLowerCase()) {\n case 'opencode':\n return <Terminal className={iconClass} />;\n case 'claude-code':\n case 'claudecode':\n return <Sparkles className={iconClass} />;\n case 'gemini-cli':\n case 'gemini':\n return <Zap className={iconClass} />;\n case 'cursor':\n return <Terminal className={iconClass} />;\n default:\n return <Bell className={iconClass} />;\n }\n}\n\n/**\n * Get the icon for an event type\n */\nfunction getEventIcon(eventType: string) {\n const iconClass = 'size-4';\n\n if (\n eventType.includes('completed') ||\n eventType.includes('success') ||\n eventType.includes('idle')\n ) {\n return <CheckCircle className={`${iconClass} text-status-success-text`} />;\n }\n if (eventType.includes('failed') || eventType.includes('error')) {\n return <AlertCircle className={`${iconClass} text-status-error-text`} />;\n }\n if (eventType.includes('question') || eventType.includes('permission')) {\n return <AlertCircle className={`${iconClass} text-status-warning-text`} />;\n }\n return <Info className={`${iconClass} text-status-info-text`} />;\n}\n\n/**\n * Get display name for a source tool\n */\nfunction getSourceDisplayName(source: string): string {\n const names: Record<string, string> = {\n opencode: 'OpenCode',\n 'opencode-vibe-webhook': 'OpenCode',\n 'claude-code': 'Claude Code',\n claudecode: 'Claude Code',\n 'gemini-cli': 'Gemini CLI',\n gemini: 'Gemini CLI',\n cursor: 'Cursor',\n };\n return names[source.toLowerCase()] || source;\n}\n\n/**\n * Get background color based on source\n */\nfunction getSourceStyles(source: string): { bg: string; border: string; text: string } {\n switch (source.toLowerCase()) {\n case 'opencode':\n case 'opencode-vibe-webhook':\n return {\n bg: 'bg-status-info-bg/10',\n border: 'border-status-info-border',\n text: 'text-status-info-text',\n };\n case 'claude-code':\n case 'claudecode':\n return {\n bg: 'bg-status-warning-bg/10',\n border: 'border-status-warning-border',\n text: 'text-status-warning-text',\n };\n case 'gemini-cli':\n case 'gemini':\n return {\n bg: 'bg-status-info-bg/10',\n border: 'border-status-info-border',\n text: 'text-status-info-text',\n };\n case 'cursor':\n return {\n bg: 'bg-status-success-bg/10',\n border: 'border-status-success-border',\n text: 'text-status-success-text',\n };\n default:\n return {\n bg: 'bg-bg-secondary',\n border: 'border-border-primary',\n text: 'text-text-secondary',\n };\n }\n}\n\n/**\n * Format timestamp for display\n */\nfunction formatTimestamp(timestamp: string): string {\n try {\n const date = new Date(timestamp);\n return date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });\n } catch {\n return '';\n }\n}\n\n/**\n * Single notification banner\n */\nfunction NotificationBanner({\n notification,\n onDismiss,\n autoDismiss = true,\n autoDismissDelay = 8000,\n}: NotificationBannerProps) {\n const [isExiting, setIsExiting] = useState(false);\n const [progress, setProgress] = useState(100);\n const styles = getSourceStyles(notification.source);\n\n useEffect(() => {\n if (!autoDismiss) return;\n\n // Progress countdown\n const startTime = Date.now();\n const interval = setInterval(() => {\n const elapsed = Date.now() - startTime;\n const remaining = Math.max(0, 100 - (elapsed / autoDismissDelay) * 100);\n setProgress(remaining);\n }, 50);\n\n // Auto-dismiss timer\n const timer = setTimeout(() => {\n setIsExiting(true);\n setTimeout(() => onDismiss(notification.id), 300);\n }, autoDismissDelay);\n\n return () => {\n clearInterval(interval);\n clearTimeout(timer);\n };\n }, [autoDismiss, autoDismissDelay, notification.id, onDismiss]);\n\n const handleDismiss = () => {\n setIsExiting(true);\n setTimeout(() => onDismiss(notification.id), 300);\n };\n\n return (\n <div\n className={`\n relative flex items-start gap-3 p-4 rounded-lg border shadow-lg backdrop-blur-sm\n ${styles.bg} ${styles.border}\n transform transition-all duration-300 ease-out\n ${isExiting ? 'opacity-0 -translate-y-2 scale-95' : 'opacity-100 translate-y-0 scale-100'}\n animate-slide-in-down\n `}\n role=\"alert\"\n data-testid=\"session-notification-banner\"\n >\n {/* Source icon */}\n <div className={`flex-shrink-0 p-2 rounded-lg bg-bg-tertiary/50 ${styles.text}`}>\n {getSourceIcon(notification.source)}\n </div>\n\n {/* Content */}\n <div className=\"flex-1 min-w-0\">\n {/* Header */}\n <div className=\"flex items-center gap-2 mb-1\">\n <span className={`text-xs font-semibold uppercase tracking-wide ${styles.text}`}>\n {getSourceDisplayName(notification.source)}\n </span>\n {getEventIcon(notification.eventType)}\n <span className=\"text-xs text-text-muted\">{formatTimestamp(notification.timestamp)}</span>\n </div>\n\n {/* Title */}\n <p className=\"text-sm font-medium text-text-primary\">{notification.title}</p>\n\n {/* Message */}\n <p className=\"text-xs text-text-secondary mt-0.5\">{notification.message}</p>\n </div>\n\n {/* Dismiss button */}\n <button\n type=\"button\"\n onClick={handleDismiss}\n className=\"flex-shrink-0 p-1 rounded-md hover:bg-bg-tertiary/50 text-text-muted hover:text-text-secondary transition-colors\"\n aria-label=\"Dismiss notification\"\n >\n <X className=\"size-4\" />\n </button>\n\n {/* Progress bar for auto-dismiss */}\n {autoDismiss && (\n <div className=\"absolute bottom-0 left-0 right-0 h-0.5 bg-bg-tertiary/30 overflow-hidden rounded-b-lg\">\n <div\n className={`h-full ${styles.text.replace('text-', 'bg-')}/50 transition-all duration-50 ease-linear`}\n style={{ width: `${progress}%` }}\n />\n </div>\n )}\n </div>\n );\n}\n\n/**\n * Props for the session notifications container\n */\nexport interface SessionNotificationsProps {\n /**\n * Maximum number of notifications to display\n */\n maxVisible?: number;\n\n /**\n * Whether notifications should auto-dismiss (default: false - requires manual close)\n */\n autoDismiss?: boolean;\n\n /**\n * Auto-dismiss delay in milliseconds (only used if autoDismiss is true)\n */\n autoDismissDelay?: number;\n\n /**\n * Whether to show the connection status indicator\n */\n showConnectionStatus?: boolean;\n\n /**\n * Compact mode: drop the standalone \"Agent Notifications\" header row and\n * render nothing at all when there are no notifications, so the banner\n * stops reserving vertical space. The connection status is expected to be\n * rendered in the page header instead (see `SessionConnectionStatus`).\n */\n compact?: boolean;\n}\n\n/**\n * Standalone agent connection-status chip for placement in a page header.\n * Reclaims the row the notification banner used to spend on a dedicated\n * \"Agent Notifications\" + status line. Renders nothing when no agent is\n * configured for the current context.\n */\nexport function SessionConnectionStatus() {\n const { connectionStatus, reconnect, agentId } = useAgentEventsContext();\n if (!agentId) return null;\n return <ConnectionStatusIndicator status={connectionStatus} onReconnect={reconnect} />;\n}\n\n/**\n * Maximum notifications to keep in session banner state\n */\nconst MAX_SESSION_NOTIFICATIONS = 50;\n\n/**\n * Container component for session-contextual notifications\n * Displays notifications above the session list with connection status\n *\n * IMPORTANT: This component maintains its OWN notification state separate from the global\n * toast system. It subscribes to the same WebSocket events via the context but stores\n * notifications independently. This allows:\n * - Global toasts to auto-dismiss without affecting session banners\n * - Session banners to persist until manually dismissed by the user\n */\nexport function SessionNotifications({\n maxVisible = 3,\n autoDismiss = false,\n autoDismissDelay = 8000,\n showConnectionStatus = true,\n compact = false,\n}: SessionNotificationsProps) {\n const tr = useTr();\n const {\n notifications: globalNotifications,\n connectionStatus,\n reconnect,\n agentId,\n } = useAgentEventsContext();\n\n // Maintain separate notification state for session banners\n const [sessionNotifications, setSessionNotifications] = useState<AiToolNotification[]>([]);\n const seenIdsRef = useRef<Set<string>>(new Set());\n\n // Sync new notifications from global state to session state\n // We only ADD new notifications, never remove them (except via manual dismiss)\n useEffect(() => {\n const newNotifications: AiToolNotification[] = [];\n\n for (const notification of globalNotifications) {\n if (!seenIdsRef.current.has(notification.id)) {\n seenIdsRef.current.add(notification.id);\n newNotifications.push(notification);\n }\n }\n\n if (newNotifications.length > 0) {\n setSessionNotifications((prev) => {\n const updated = [...newNotifications, ...prev];\n return updated.slice(0, MAX_SESSION_NOTIFICATIONS);\n });\n }\n }, [globalNotifications]);\n\n // Handle manual dismiss - only affects session banner state\n const handleDismiss = useCallback((id: string) => {\n setSessionNotifications((prev) => prev.filter((n) => n.id !== id));\n }, []);\n\n const visibleNotifications = sessionNotifications.slice(0, maxVisible);\n const hiddenCount = Math.max(0, sessionNotifications.length - maxVisible);\n\n // Don't render anything if no agent is configured. In compact mode we also\n // render nothing while there are no notifications so the banner stops\n // reserving a wasted row (the connection status lives in the page header).\n if (!agentId && sessionNotifications.length === 0) {\n return null;\n }\n if (compact && sessionNotifications.length === 0) {\n return null;\n }\n\n return (\n <div\n className={compact ? 'space-y-2' : 'mb-4 space-y-3'}\n data-testid=\"session-notifications-container\"\n >\n {/* Connection status indicator — header row only in full mode. In\n compact mode the status is rendered in the page header instead. */}\n {!compact && showConnectionStatus && agentId && (\n <div className=\"flex items-center justify-between\">\n <div className=\"text-xs text-text-muted\">\n {tr(\n 'vibecontrols.shared.sessionNotificationBanner.agentNotifications',\n 'Agent Notifications'\n )}\n </div>\n <ConnectionStatusIndicator status={connectionStatus} onReconnect={reconnect} />\n </div>\n )}\n\n {/* Notification banners */}\n <div className=\"space-y-2\">\n {visibleNotifications.map((notification) => (\n <NotificationBanner\n key={notification.id}\n notification={notification}\n onDismiss={handleDismiss}\n autoDismiss={autoDismiss}\n autoDismissDelay={autoDismissDelay}\n />\n ))}\n </div>\n\n {/* Hidden count indicator */}\n {hiddenCount > 0 && (\n <div className=\"text-xs text-text-muted text-center py-1\">\n +{hiddenCount}{' '}\n {tr(\n 'vibecontrols.shared.sessionNotificationBanner.moreNotifications',\n 'more notification(s)'\n )}\n </div>\n )}\n </div>\n );\n}\n\nexport default SessionNotifications;\n"],"mappings":"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"}
|
|
@@ -51,10 +51,10 @@ function o({ status: o, variant: s, size: c = "sm", showDot: l = !0, pulse: u =
|
|
|
51
51
|
let d = s || r[o] || "neutral", f = o.replace(/_/g, " ").toLowerCase(), p = {
|
|
52
52
|
sm: "text-xs px-2 py-0.5",
|
|
53
53
|
md: "text-sm px-2.5 py-1"
|
|
54
|
-
}, m = c === "sm" ? "size-2" : "size-2.5";
|
|
54
|
+
}, m = c === "sm" ? "size-2" : "size-2.5", h = /^(ACTIVE|RUNNING|connected|connecting|STARTING|healthy)$/i.test(o), g = u || h;
|
|
55
55
|
return /* @__PURE__ */ n("span", {
|
|
56
56
|
className: `inline-flex items-center gap-1.5 font-medium rounded-full capitalize ${p[c]} ${i[d]}`,
|
|
57
|
-
children: [l && /* @__PURE__ */ t(e, { className: `${m} fill-current ${a[d]} ${
|
|
57
|
+
children: [l && /* @__PURE__ */ t(e, { className: `${m} fill-current ${a[d]} ${g ? "motion-safe:animate-vc-glow-pulse" : ""}` }), f]
|
|
58
58
|
});
|
|
59
59
|
}
|
|
60
60
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"StatusBadge.js","names":[],"sources":["../../../src/components/shared/StatusBadge.tsx"],"sourcesContent":["import { Circle } from 'lucide-react';\n\ntype StatusVariant = 'success' | 'warning' | 'error' | 'info' | 'neutral';\n\ninterface StatusBadgeProps {\n status: string;\n variant?: StatusVariant;\n size?: 'sm' | 'md';\n showDot?: boolean;\n pulse?: boolean;\n}\n\nconst variantMap: Record<string, StatusVariant> = {\n // Agent statuses\n ACTIVE: 'success',\n RUNNING: 'success',\n IDLE: 'info',\n REGISTERING: 'info',\n DEGRADED: 'warning',\n OFFLINE: 'error',\n DELETED: 'neutral',\n // Session statuses\n PENDING: 'info',\n STARTING: 'info',\n STOPPING: 'warning',\n STOPPED: 'neutral',\n ERROR: 'error',\n TERMINATED: 'neutral',\n // Vibe statuses\n INITIALIZING: 'info',\n INACTIVE: 'neutral',\n ARCHIVED: 'neutral',\n // Port forward statuses\n CONFLICT: 'error',\n // Module statuses\n DRAFT: 'neutral',\n PENDING_REVIEW: 'info',\n APPROVED: 'success',\n REJECTED: 'error',\n DEPRECATED: 'warning',\n // Installation statuses\n INSTALLING: 'info',\n UNINSTALLING: 'warning',\n // Connection statuses\n connected: 'success',\n disconnected: 'neutral',\n connecting: 'info',\n error: 'error',\n // Health statuses\n healthy: 'success',\n degraded: 'warning',\n unhealthy: 'error',\n unknown: 'neutral',\n};\n\n// `info` (connecting / installing transient states) reads NEUTRAL rather than\n// off-brand blue — the product brand is purple and loud blue status pills\n// clash with it. Semantic hues stay for success/warning/error; purple is\n// reserved for primary actions, not badges. All use the subtle recipe so they\n// pass AA in light and dark.\nconst variantStyles: Record<StatusVariant, string> = {\n success: 'bg-status-success-bg-subtle text-status-success-text',\n warning: 'bg-status-warning-bg-subtle text-status-warning-text',\n error: 'bg-status-error-bg-subtle text-status-error-text',\n info: 'bg-bg-sunken text-text-secondary',\n neutral: 'bg-bg-sunken text-text-secondary',\n};\n\nconst dotStyles: Record<StatusVariant, string> = {\n success: 'text-status-success-text',\n warning: 'text-status-warning-text',\n error: 'text-status-error-text',\n info: 'text-text-secondary',\n neutral: 'text-text-secondary',\n};\n\nexport function StatusBadge({\n status,\n variant,\n size = 'sm',\n showDot = true,\n pulse = false,\n}: StatusBadgeProps) {\n const resolvedVariant = variant || variantMap[status] || 'neutral';\n const displayStatus = status.replace(/_/g, ' ').toLowerCase();\n\n const sizeStyles = {\n sm: 'text-xs px-2 py-0.5',\n md: 'text-sm px-2.5 py-1',\n };\n\n const dotSize = size === 'sm' ? 'size-2' : 'size-2.5';\n\n return (\n <span\n className={`inline-flex items-center gap-1.5 font-medium rounded-full capitalize ${sizeStyles[size]} ${variantStyles[resolvedVariant]}`}\n >\n {showDot && (\n <Circle\n className={`${dotSize} fill-current ${dotStyles[resolvedVariant]} ${
|
|
1
|
+
{"version":3,"file":"StatusBadge.js","names":[],"sources":["../../../src/components/shared/StatusBadge.tsx"],"sourcesContent":["import { Circle } from 'lucide-react';\n\ntype StatusVariant = 'success' | 'warning' | 'error' | 'info' | 'neutral';\n\ninterface StatusBadgeProps {\n status: string;\n variant?: StatusVariant;\n size?: 'sm' | 'md';\n showDot?: boolean;\n pulse?: boolean;\n}\n\nconst variantMap: Record<string, StatusVariant> = {\n // Agent statuses\n ACTIVE: 'success',\n RUNNING: 'success',\n IDLE: 'info',\n REGISTERING: 'info',\n DEGRADED: 'warning',\n OFFLINE: 'error',\n DELETED: 'neutral',\n // Session statuses\n PENDING: 'info',\n STARTING: 'info',\n STOPPING: 'warning',\n STOPPED: 'neutral',\n ERROR: 'error',\n TERMINATED: 'neutral',\n // Vibe statuses\n INITIALIZING: 'info',\n INACTIVE: 'neutral',\n ARCHIVED: 'neutral',\n // Port forward statuses\n CONFLICT: 'error',\n // Module statuses\n DRAFT: 'neutral',\n PENDING_REVIEW: 'info',\n APPROVED: 'success',\n REJECTED: 'error',\n DEPRECATED: 'warning',\n // Installation statuses\n INSTALLING: 'info',\n UNINSTALLING: 'warning',\n // Connection statuses\n connected: 'success',\n disconnected: 'neutral',\n connecting: 'info',\n error: 'error',\n // Health statuses\n healthy: 'success',\n degraded: 'warning',\n unhealthy: 'error',\n unknown: 'neutral',\n};\n\n// `info` (connecting / installing transient states) reads NEUTRAL rather than\n// off-brand blue — the product brand is purple and loud blue status pills\n// clash with it. Semantic hues stay for success/warning/error; purple is\n// reserved for primary actions, not badges. All use the subtle recipe so they\n// pass AA in light and dark.\nconst variantStyles: Record<StatusVariant, string> = {\n success: 'bg-status-success-bg-subtle text-status-success-text',\n warning: 'bg-status-warning-bg-subtle text-status-warning-text',\n error: 'bg-status-error-bg-subtle text-status-error-text',\n info: 'bg-bg-sunken text-text-secondary',\n neutral: 'bg-bg-sunken text-text-secondary',\n};\n\nconst dotStyles: Record<StatusVariant, string> = {\n success: 'text-status-success-text',\n warning: 'text-status-warning-text',\n error: 'text-status-error-text',\n info: 'text-text-secondary',\n neutral: 'text-text-secondary',\n};\n\nexport function StatusBadge({\n status,\n variant,\n size = 'sm',\n showDot = true,\n pulse = false,\n}: StatusBadgeProps) {\n const resolvedVariant = variant || variantMap[status] || 'neutral';\n const displayStatus = status.replace(/_/g, ' ').toLowerCase();\n\n const sizeStyles = {\n sm: 'text-xs px-2 py-0.5',\n md: 'text-sm px-2.5 py-1',\n };\n\n const dotSize = size === 'sm' ? 'size-2' : 'size-2.5';\n\n // Boff UI 2.0 — live/active/running statuses get a calm reduced-motion-safe\n // opacity glow-pulse on the status dot (no movement). Honour an explicit\n // `pulse` request too. The pulse is purely cosmetic on the dot.\n const isLive = /^(ACTIVE|RUNNING|connected|connecting|STARTING|healthy)$/i.test(status);\n const pulseDot = pulse || isLive;\n\n return (\n <span\n className={`inline-flex items-center gap-1.5 font-medium rounded-full capitalize ${sizeStyles[size]} ${variantStyles[resolvedVariant]}`}\n >\n {showDot && (\n <Circle\n className={`${dotSize} fill-current ${dotStyles[resolvedVariant]} ${pulseDot ? 'motion-safe:animate-vc-glow-pulse' : ''}`}\n />\n )}\n {displayStatus}\n </span>\n );\n}\n"],"mappings":";;;AAYA,IAAM,IAA4C;CAEhD,QAAQ;CACR,SAAS;CACT,MAAM;CACN,aAAa;CACb,UAAU;CACV,SAAS;CACT,SAAS;CAET,SAAS;CACT,UAAU;CACV,UAAU;CACV,SAAS;CACT,OAAO;CACP,YAAY;CAEZ,cAAc;CACd,UAAU;CACV,UAAU;CAEV,UAAU;CAEV,OAAO;CACP,gBAAgB;CAChB,UAAU;CACV,UAAU;CACV,YAAY;CAEZ,YAAY;CACZ,cAAc;CAEd,WAAW;CACX,cAAc;CACd,YAAY;CACZ,OAAO;CAEP,SAAS;CACT,UAAU;CACV,WAAW;CACX,SAAS;CACV,EAOK,IAA+C;CACnD,SAAS;CACT,SAAS;CACT,OAAO;CACP,MAAM;CACN,SAAS;CACV,EAEK,IAA2C;CAC/C,SAAS;CACT,SAAS;CACT,OAAO;CACP,MAAM;CACN,SAAS;CACV;AAED,SAAgB,EAAY,EAC1B,WACA,YACA,UAAO,MACP,aAAU,IACV,WAAQ,MACW;CACnB,IAAM,IAAkB,KAAW,EAAW,MAAW,WACnD,IAAgB,EAAO,QAAQ,MAAM,IAAI,CAAC,aAAa,EAEvD,IAAa;EACjB,IAAI;EACJ,IAAI;EACL,EAEK,IAAU,MAAS,OAAO,WAAW,YAKrC,IAAS,4DAA4D,KAAK,EAAO,EACjF,IAAW,KAAS;AAE1B,QACE,kBAAC,QAAD;EACE,WAAW,wEAAwE,EAAW,GAAM,GAAG,EAAc;YADvH,CAGG,KACC,kBAAC,GAAD,EACE,WAAW,GAAG,EAAQ,gBAAgB,EAAU,GAAiB,GAAG,IAAW,sCAAsC,MACrH,CAAA,EAEH,EACI"}
|