@burdenoff/microfe-vibecontrols 2026.530.3 โ†’ 2026.530.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -85,7 +85,11 @@ var v = ({ agent: v, onSelect: y, onConfigure: b, onSettings: x, onStart: S, onS
85
85
  className: "flex items-center gap-1 sm:gap-2 flex-shrink-0",
86
86
  children: [/* @__PURE__ */ g(t, {
87
87
  status: F,
88
- reason: k ?? null
88
+ reason: k ?? null,
89
+ linkTo: `/vibecontrols/agents/${v.id}?tab=connection`,
90
+ onClick: (e) => {
91
+ e.stopPropagation();
92
+ }
89
93
  }), (M || S || C) && /* @__PURE__ */ _("div", {
90
94
  className: "relative",
91
95
  children: [/* @__PURE__ */ g("button", {
@@ -1 +1 @@
1
- {"version":3,"file":"AgentCard.js","names":[],"sources":["../../../src/components/agents/AgentCard.tsx"],"sourcesContent":["import type { FC } from 'react';\nimport { useState } from 'react';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { motion, AnimatePresence } from 'framer-motion';\nimport {\n Server,\n Clock,\n Cpu,\n MoreVertical,\n Play,\n Square,\n Settings,\n Globe,\n Link,\n RefreshCw,\n} from 'lucide-react';\nimport { AgentStatusBadge } from './AgentStatusBadge';\nimport type { Agent, AgentStatus } from './types';\nimport { getAgentStatus } from './agentStatus';\n\nexport interface AgentCardProps {\n agent: Agent;\n onSelect?: (agent: Agent) => void;\n onConfigure?: (agent: Agent) => void;\n onSettings?: (agent: Agent) => void;\n onStart?: (agent: Agent) => void;\n onStop?: (agent: Agent) => void;\n onDisconnect?: (agent: Agent) => void;\n onEditTunnelUrl?: (agent: Agent) => void;\n selected?: boolean;\n onToggleSelect?: () => void;\n className?: string;\n /**\n * Last error from /health/ready or the live probe โ€” shown in the badge\n * tooltip when the agent is degraded so users know WHY without curling\n * the tunnel themselves.\n */\n healthErrorMessage?: string | null;\n}\n\n/**\n * AgentCard - Display agent information in a card format\n *\n * @example\n * ```tsx\n * <AgentCard\n * agent={myAgent}\n * onSelect={handleSelect}\n * onConfigure={handleConfigure}\n * />\n * ```\n */\nexport const AgentCard: FC<AgentCardProps> = ({\n agent,\n onSelect,\n onConfigure,\n onSettings,\n onStart,\n onStop,\n onDisconnect,\n onEditTunnelUrl,\n selected = false,\n onToggleSelect,\n className = '',\n healthErrorMessage,\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 // Support both onConfigure and onSettings\n const handleConfigure = onConfigure || onSettings;\n const [showMenu, setShowMenu] = useState(false);\n\n const status = getAgentStatus(agent);\n\n const formatLastHeartbeat = (timestamp: string | null | undefined) => {\n if (!timestamp) return tr('agentsPage.never', 'Never');\n const diff = Date.now() - new Date(timestamp).getTime();\n if (diff < 60000) return tr('agentsPage.justNow', 'Just now');\n if (diff < 3600000) return `${Math.floor(diff / 60000)}m ago`;\n if (diff < 86400000) return `${Math.floor(diff / 3600000)}h ago`;\n return `${Math.floor(diff / 86400000)}d ago`;\n };\n\n const platformIcon =\n {\n darwin: '๐ŸŽ',\n linux: '๐Ÿง',\n windows: '๐ŸชŸ',\n }[agent.platform || ''] || '๐Ÿ’ป';\n\n return (\n <motion.div\n whileHover={{ y: -2, transition: { duration: 0.15, ease: [0.4, 0, 0.2, 1] } }}\n whileTap={{ scale: 0.98 }}\n className={`bg-bg-surface border rounded-lg p-4 h-full hover:border-border-strong hover:shadow-md transition-all cursor-pointer ${\n selected\n ? 'border-action-primary-bg ring-1 ring-action-primary-bg/30'\n : 'border-border-default'\n } ${className}`}\n onClick={() => onSelect?.(agent)}\n >\n <div className=\"flex items-start justify-between gap-2\">\n <div className=\"flex items-start gap-2 sm:gap-3 min-w-0 flex-1\">\n {/* Selection Checkbox */}\n {onToggleSelect && (\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation();\n onToggleSelect();\n }}\n className={`flex-shrink-0 size-5 min-w-[1.25rem] min-h-[1.25rem] rounded border transition-colors mt-0.5 ${\n selected\n ? 'bg-action-primary-bg border-action-primary-bg text-action-primary-text'\n : 'border-border-default hover:border-border-strong bg-bg-surface'\n } flex items-center justify-center`}\n >\n {selected && (\n <svg\n className=\"size-3\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n strokeWidth={3}\n >\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M5 13l4 4L19 7\" />\n </svg>\n )}\n </button>\n )}\n <div className=\"flex-shrink-0 size-8 sm:w-10 sm:h-10 rounded-lg bg-bg-sunken flex items-center justify-center\">\n <Server className=\"size-4 sm:w-5 sm:h-5 text-text-secondary\" />\n </div>\n <div className=\"min-w-0 flex-1\">\n <div className=\"flex items-center gap-1.5 sm:gap-2 min-w-0\">\n <h3 className=\"font-semibold text-text-primary truncate min-w-0 max-w-[120px] sm:max-w-none text-sm sm:text-base\">\n {agent.name}\n </h3>\n <span className=\"text-base sm:text-lg flex-shrink-0\">{platformIcon}</span>\n </div>\n <p className=\"text-xs sm:text-sm text-text-secondary truncate\">\n {agent.hostname || `ID: ${agent.id}`}\n </p>\n </div>\n </div>\n {/* Status and Actions - always visible */}\n <div className=\"flex items-center gap-1 sm:gap-2 flex-shrink-0\">\n <AgentStatusBadge status={status} reason={healthErrorMessage ?? null} />\n {(handleConfigure || onStart || onStop) && (\n <div className=\"relative\">\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation();\n setShowMenu(!showMenu);\n }}\n className=\"p-1 sm:p-1.5 rounded hover:bg-bg-sunken transition-colors flex items-center justify-center\"\n aria-label=\"Agent actions menu\"\n >\n <MoreVertical className=\"size-4 text-text-primary opacity-70 hover:opacity-100\" />\n </button>\n <AnimatePresence>\n {showMenu && (\n <motion.div\n initial={{ opacity: 0, scale: 0.95, y: -4 }}\n animate={{ opacity: 1, scale: 1, y: 0 }}\n exit={{ opacity: 0, scale: 0.95, y: -4 }}\n transition={{ duration: 0.15 }}\n className=\"absolute right-0 mt-1 w-40 bg-bg-surface border border-border-default rounded-lg shadow-lg z-10\"\n >\n {status === 'ACTIVE' && onStop && (\n <motion.button\n whileTap={{ scale: 0.97 }}\n onClick={(e) => {\n e.stopPropagation();\n onStop(agent);\n setShowMenu(false);\n }}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken rounded-t-lg\"\n >\n <Square className=\"size-4\" />\n {tr('agentsPage.stopAgent', 'Stop Agent')}\n </motion.button>\n )}\n {(status === 'STOPPED' || status === 'OFFLINE') && onStart && (\n <motion.button\n whileTap={{ scale: 0.97 }}\n onClick={(e) => {\n e.stopPropagation();\n onStart(agent);\n setShowMenu(false);\n }}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken rounded-t-lg\"\n >\n <Play className=\"size-4\" />\n {tr('agentsPage.startAgent', 'Start Agent')}\n </motion.button>\n )}\n {status === 'DEGRADED' && onStart && (\n // Reconnect on a degraded agent fires the same\n // startVibecontrolsAgent mutation as a fresh start โ€”\n // svc-side that POSTs lifecycle/start to the agent's\n // tunnel, which usually clears transient failures\n // (tunnel hiccup, finalize retry). Labelled\n // \"Reconnect\" not \"Start\" so the user knows it's a\n // recovery action, not creating a new instance.\n <motion.button\n whileTap={{ scale: 0.97 }}\n onClick={(e) => {\n e.stopPropagation();\n onStart(agent);\n setShowMenu(false);\n }}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken rounded-t-lg\"\n title={\n healthErrorMessage\n ? `Retry lifecycle/start โ€” last error: ${healthErrorMessage}`\n : 'Retry lifecycle/start'\n }\n >\n <RefreshCw className=\"size-4\" />\n {tr('agentsPage.reconnectAgent', 'Reconnect')}\n </motion.button>\n )}\n {(status === 'ACTIVE' ||\n status === 'STOPPED' ||\n status === 'OFFLINE' ||\n status === 'DEGRADED') &&\n onDisconnect && (\n <motion.button\n whileTap={{ scale: 0.97 }}\n onClick={(e) => {\n e.stopPropagation();\n onDisconnect(agent);\n setShowMenu(false);\n }}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-sm text-action-danger-text hover:bg-bg-sunken\"\n >\n <Square className=\"size-4\" />\n {status === 'OFFLINE' ? 'Clear Connection' : 'Disconnect'}\n </motion.button>\n )}\n {onEditTunnelUrl && (\n <motion.button\n whileTap={{ scale: 0.97 }}\n onClick={(e) => {\n e.stopPropagation();\n onEditTunnelUrl(agent);\n setShowMenu(false);\n }}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken\"\n >\n <Link className=\"size-4\" />\n Edit Tunnel\n </motion.button>\n )}\n {handleConfigure && (\n <motion.button\n whileTap={{ scale: 0.97 }}\n onClick={(e) => {\n e.stopPropagation();\n handleConfigure(agent);\n setShowMenu(false);\n }}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken rounded-b-lg\"\n >\n <Settings className=\"size-4\" />\n Settings\n </motion.button>\n )}\n </motion.div>\n )}\n </AnimatePresence>\n </div>\n )}\n </div>\n </div>\n\n <div className=\"mt-4 grid grid-cols-2 gap-3 text-sm\">\n <div className=\"flex items-center gap-2 text-text-secondary\">\n <Clock className=\"size-4\" />\n <span>{formatLastHeartbeat(agent.lastHeartbeat)}</span>\n </div>\n {agent.architecture && (\n <div className=\"flex items-center gap-2 text-text-secondary\">\n <Cpu className=\"size-4\" />\n <span>{agent.architecture}</span>\n </div>\n )}\n </div>\n\n {agent.tunnelUrl && (\n // Plain \"Tunnel: <link>\" row. The colored status chip that used\n // to sit here was a redundant second status surface โ€” the\n // canonical state lives in the <AgentStatusBadge> top-right.\n // The Globe glyph stays as a neutral icon (no status color).\n <div className=\"mt-3 flex items-center gap-2 text-sm\">\n <Globe className=\"size-4 flex-shrink-0 text-text-secondary\" />\n <span className=\"text-text-secondary flex-shrink-0\">Tunnel:</span>\n <a\n href={agent.tunnelUrl}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n onClick={(e) => e.stopPropagation()}\n className=\"text-action-primary-text hover:underline truncate\"\n >\n {agent.tunnelUrl}\n </a>\n </div>\n )}\n\n {agent.capabilities && agent.capabilities.length > 0 && (\n <div className=\"mt-3 flex flex-wrap gap-1\">\n {agent.capabilities.slice(0, 4).map((cap) => (\n <span\n key={cap}\n className=\"px-2 py-0.5 text-xs bg-bg-sunken text-text-secondary rounded\"\n >\n {cap}\n </span>\n ))}\n {agent.capabilities.length > 4 && (\n <span className=\"px-2 py-0.5 text-xs bg-bg-sunken text-text-secondary rounded\">\n +{agent.capabilities.length - 4}\n </span>\n )}\n </div>\n )}\n </motion.div>\n );\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"AgentCard.js","names":[],"sources":["../../../src/components/agents/AgentCard.tsx"],"sourcesContent":["import type { FC } from 'react';\nimport { useState } from 'react';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { motion, AnimatePresence } from 'framer-motion';\nimport {\n Server,\n Clock,\n Cpu,\n MoreVertical,\n Play,\n Square,\n Settings,\n Globe,\n Link,\n RefreshCw,\n} from 'lucide-react';\nimport { AgentStatusBadge } from './AgentStatusBadge';\nimport type { Agent, AgentStatus } from './types';\nimport { getAgentStatus } from './agentStatus';\n\nexport interface AgentCardProps {\n agent: Agent;\n onSelect?: (agent: Agent) => void;\n onConfigure?: (agent: Agent) => void;\n onSettings?: (agent: Agent) => void;\n onStart?: (agent: Agent) => void;\n onStop?: (agent: Agent) => void;\n onDisconnect?: (agent: Agent) => void;\n onEditTunnelUrl?: (agent: Agent) => void;\n selected?: boolean;\n onToggleSelect?: () => void;\n className?: string;\n /**\n * Last error from /health/ready or the live probe โ€” shown in the badge\n * tooltip when the agent is degraded so users know WHY without curling\n * the tunnel themselves.\n */\n healthErrorMessage?: string | null;\n}\n\n/**\n * AgentCard - Display agent information in a card format\n *\n * @example\n * ```tsx\n * <AgentCard\n * agent={myAgent}\n * onSelect={handleSelect}\n * onConfigure={handleConfigure}\n * />\n * ```\n */\nexport const AgentCard: FC<AgentCardProps> = ({\n agent,\n onSelect,\n onConfigure,\n onSettings,\n onStart,\n onStop,\n onDisconnect,\n onEditTunnelUrl,\n selected = false,\n onToggleSelect,\n className = '',\n healthErrorMessage,\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 // Support both onConfigure and onSettings\n const handleConfigure = onConfigure || onSettings;\n const [showMenu, setShowMenu] = useState(false);\n\n const status = getAgentStatus(agent);\n\n const formatLastHeartbeat = (timestamp: string | null | undefined) => {\n if (!timestamp) return tr('agentsPage.never', 'Never');\n const diff = Date.now() - new Date(timestamp).getTime();\n if (diff < 60000) return tr('agentsPage.justNow', 'Just now');\n if (diff < 3600000) return `${Math.floor(diff / 60000)}m ago`;\n if (diff < 86400000) return `${Math.floor(diff / 3600000)}h ago`;\n return `${Math.floor(diff / 86400000)}d ago`;\n };\n\n const platformIcon =\n {\n darwin: '๐ŸŽ',\n linux: '๐Ÿง',\n windows: '๐ŸชŸ',\n }[agent.platform || ''] || '๐Ÿ’ป';\n\n return (\n <motion.div\n whileHover={{ y: -2, transition: { duration: 0.15, ease: [0.4, 0, 0.2, 1] } }}\n whileTap={{ scale: 0.98 }}\n className={`bg-bg-surface border rounded-lg p-4 h-full hover:border-border-strong hover:shadow-md transition-all cursor-pointer ${\n selected\n ? 'border-action-primary-bg ring-1 ring-action-primary-bg/30'\n : 'border-border-default'\n } ${className}`}\n onClick={() => onSelect?.(agent)}\n >\n <div className=\"flex items-start justify-between gap-2\">\n <div className=\"flex items-start gap-2 sm:gap-3 min-w-0 flex-1\">\n {/* Selection Checkbox */}\n {onToggleSelect && (\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation();\n onToggleSelect();\n }}\n className={`flex-shrink-0 size-5 min-w-[1.25rem] min-h-[1.25rem] rounded border transition-colors mt-0.5 ${\n selected\n ? 'bg-action-primary-bg border-action-primary-bg text-action-primary-text'\n : 'border-border-default hover:border-border-strong bg-bg-surface'\n } flex items-center justify-center`}\n >\n {selected && (\n <svg\n className=\"size-3\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n strokeWidth={3}\n >\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M5 13l4 4L19 7\" />\n </svg>\n )}\n </button>\n )}\n <div className=\"flex-shrink-0 size-8 sm:w-10 sm:h-10 rounded-lg bg-bg-sunken flex items-center justify-center\">\n <Server className=\"size-4 sm:w-5 sm:h-5 text-text-secondary\" />\n </div>\n <div className=\"min-w-0 flex-1\">\n <div className=\"flex items-center gap-1.5 sm:gap-2 min-w-0\">\n <h3 className=\"font-semibold text-text-primary truncate min-w-0 max-w-[120px] sm:max-w-none text-sm sm:text-base\">\n {agent.name}\n </h3>\n <span className=\"text-base sm:text-lg flex-shrink-0\">{platformIcon}</span>\n </div>\n <p className=\"text-xs sm:text-sm text-text-secondary truncate\">\n {agent.hostname || `ID: ${agent.id}`}\n </p>\n </div>\n </div>\n {/* Status and Actions - always visible */}\n <div className=\"flex items-center gap-1 sm:gap-2 flex-shrink-0\">\n <AgentStatusBadge\n status={status}\n reason={healthErrorMessage ?? null}\n linkTo={`/vibecontrols/agents/${agent.id}?tab=connection`}\n onClick={(event) => {\n // The card itself has a `onSelect` click target that\n // routes to the agent detail. Stop propagation so a\n // badge click doesn't *also* fire that handler โ€” the\n // anchor's href already routes to the right tab and\n // React Router picks it up via the surrounding shell.\n event.stopPropagation();\n }}\n />\n {(handleConfigure || onStart || onStop) && (\n <div className=\"relative\">\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation();\n setShowMenu(!showMenu);\n }}\n className=\"p-1 sm:p-1.5 rounded hover:bg-bg-sunken transition-colors flex items-center justify-center\"\n aria-label=\"Agent actions menu\"\n >\n <MoreVertical className=\"size-4 text-text-primary opacity-70 hover:opacity-100\" />\n </button>\n <AnimatePresence>\n {showMenu && (\n <motion.div\n initial={{ opacity: 0, scale: 0.95, y: -4 }}\n animate={{ opacity: 1, scale: 1, y: 0 }}\n exit={{ opacity: 0, scale: 0.95, y: -4 }}\n transition={{ duration: 0.15 }}\n className=\"absolute right-0 mt-1 w-40 bg-bg-surface border border-border-default rounded-lg shadow-lg z-10\"\n >\n {status === 'ACTIVE' && onStop && (\n <motion.button\n whileTap={{ scale: 0.97 }}\n onClick={(e) => {\n e.stopPropagation();\n onStop(agent);\n setShowMenu(false);\n }}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken rounded-t-lg\"\n >\n <Square className=\"size-4\" />\n {tr('agentsPage.stopAgent', 'Stop Agent')}\n </motion.button>\n )}\n {(status === 'STOPPED' || status === 'OFFLINE') && onStart && (\n <motion.button\n whileTap={{ scale: 0.97 }}\n onClick={(e) => {\n e.stopPropagation();\n onStart(agent);\n setShowMenu(false);\n }}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken rounded-t-lg\"\n >\n <Play className=\"size-4\" />\n {tr('agentsPage.startAgent', 'Start Agent')}\n </motion.button>\n )}\n {status === 'DEGRADED' && onStart && (\n // Reconnect on a degraded agent fires the same\n // startVibecontrolsAgent mutation as a fresh start โ€”\n // svc-side that POSTs lifecycle/start to the agent's\n // tunnel, which usually clears transient failures\n // (tunnel hiccup, finalize retry). Labelled\n // \"Reconnect\" not \"Start\" so the user knows it's a\n // recovery action, not creating a new instance.\n <motion.button\n whileTap={{ scale: 0.97 }}\n onClick={(e) => {\n e.stopPropagation();\n onStart(agent);\n setShowMenu(false);\n }}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken rounded-t-lg\"\n title={\n healthErrorMessage\n ? `Retry lifecycle/start โ€” last error: ${healthErrorMessage}`\n : 'Retry lifecycle/start'\n }\n >\n <RefreshCw className=\"size-4\" />\n {tr('agentsPage.reconnectAgent', 'Reconnect')}\n </motion.button>\n )}\n {(status === 'ACTIVE' ||\n status === 'STOPPED' ||\n status === 'OFFLINE' ||\n status === 'DEGRADED') &&\n onDisconnect && (\n <motion.button\n whileTap={{ scale: 0.97 }}\n onClick={(e) => {\n e.stopPropagation();\n onDisconnect(agent);\n setShowMenu(false);\n }}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-sm text-action-danger-text hover:bg-bg-sunken\"\n >\n <Square className=\"size-4\" />\n {status === 'OFFLINE' ? 'Clear Connection' : 'Disconnect'}\n </motion.button>\n )}\n {onEditTunnelUrl && (\n <motion.button\n whileTap={{ scale: 0.97 }}\n onClick={(e) => {\n e.stopPropagation();\n onEditTunnelUrl(agent);\n setShowMenu(false);\n }}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken\"\n >\n <Link className=\"size-4\" />\n Edit Tunnel\n </motion.button>\n )}\n {handleConfigure && (\n <motion.button\n whileTap={{ scale: 0.97 }}\n onClick={(e) => {\n e.stopPropagation();\n handleConfigure(agent);\n setShowMenu(false);\n }}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken rounded-b-lg\"\n >\n <Settings className=\"size-4\" />\n Settings\n </motion.button>\n )}\n </motion.div>\n )}\n </AnimatePresence>\n </div>\n )}\n </div>\n </div>\n\n <div className=\"mt-4 grid grid-cols-2 gap-3 text-sm\">\n <div className=\"flex items-center gap-2 text-text-secondary\">\n <Clock className=\"size-4\" />\n <span>{formatLastHeartbeat(agent.lastHeartbeat)}</span>\n </div>\n {agent.architecture && (\n <div className=\"flex items-center gap-2 text-text-secondary\">\n <Cpu className=\"size-4\" />\n <span>{agent.architecture}</span>\n </div>\n )}\n </div>\n\n {agent.tunnelUrl && (\n // Plain \"Tunnel: <link>\" row. The colored status chip that used\n // to sit here was a redundant second status surface โ€” the\n // canonical state lives in the <AgentStatusBadge> top-right.\n // The Globe glyph stays as a neutral icon (no status color).\n <div className=\"mt-3 flex items-center gap-2 text-sm\">\n <Globe className=\"size-4 flex-shrink-0 text-text-secondary\" />\n <span className=\"text-text-secondary flex-shrink-0\">Tunnel:</span>\n <a\n href={agent.tunnelUrl}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n onClick={(e) => e.stopPropagation()}\n className=\"text-action-primary-text hover:underline truncate\"\n >\n {agent.tunnelUrl}\n </a>\n </div>\n )}\n\n {agent.capabilities && agent.capabilities.length > 0 && (\n <div className=\"mt-3 flex flex-wrap gap-1\">\n {agent.capabilities.slice(0, 4).map((cap) => (\n <span\n key={cap}\n className=\"px-2 py-0.5 text-xs bg-bg-sunken text-text-secondary rounded\"\n >\n {cap}\n </span>\n ))}\n {agent.capabilities.length > 4 && (\n <span className=\"px-2 py-0.5 text-xs bg-bg-sunken text-text-secondary rounded\">\n +{agent.capabilities.length - 4}\n </span>\n )}\n </div>\n )}\n </motion.div>\n );\n};\n"],"mappings":"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"}
@@ -61,7 +61,11 @@ function g({ agent: e, onSelect: t, onStart: s, onStop: d, onDisconnect: g, onSe
61
61
  })]
62
62
  })
63
63
  ]
64
- }), /* @__PURE__ */ f(r, { status: n(e) })]
64
+ }), /* @__PURE__ */ f(r, {
65
+ status: n(e),
66
+ linkTo: `/vibecontrols/agents/${e.id}?tab=connection`,
67
+ onClick: (e) => e.stopPropagation()
68
+ })]
65
69
  }),
66
70
  /* @__PURE__ */ p("div", {
67
71
  className: "mt-3 flex flex-wrap items-center gap-x-4 gap-y-2 text-xs text-text-secondary",
@@ -168,7 +172,11 @@ function _({ agent: e, onSelect: t, onStart: s, onStop: d, onDisconnect: g, onSe
168
172
  }),
169
173
  /* @__PURE__ */ f("td", {
170
174
  className: "px-4 py-3",
171
- children: /* @__PURE__ */ f(r, { status: n(e) })
175
+ children: /* @__PURE__ */ f(r, {
176
+ status: n(e),
177
+ linkTo: `/vibecontrols/agents/${e.id}?tab=connection`,
178
+ onClick: (e) => e.stopPropagation()
179
+ })
172
180
  }),
173
181
  /* @__PURE__ */ f("td", {
174
182
  className: "px-4 py-3",
@@ -1 +1 @@
1
- {"version":3,"file":"AgentList.js","names":[],"sources":["../../../src/components/agents/AgentList.tsx"],"sourcesContent":["import type { Agent, AgentStatus } from './types';\nimport { AgentStatusBadge } from './AgentStatusBadge';\nimport { EmptyState } from '../shared/EmptyState';\nimport { Server, Plus, Clock, Play, Square, Settings, Link2Off } from 'lucide-react';\nimport { useTr } from '../../shared/hooks/useTr';\nimport { getAgentStatus } from './agentStatus';\n\n/**\n * Format a timestamp as a relative time string (e.g. \"5m ago\", \"2h ago\")\n */\nfunction formatLastHeartbeat(timestamp: string | null | undefined): string {\n if (!timestamp) return 'Never';\n const diff = Date.now() - new Date(timestamp).getTime();\n if (diff < 60000) return 'Just now';\n if (diff < 3600000) return `${Math.floor(diff / 60000)}m ago`;\n if (diff < 86400000) return `${Math.floor(diff / 3600000)}h ago`;\n return `${Math.floor(diff / 86400000)}d ago`;\n}\n\ninterface AgentListProps {\n agents: Agent[];\n onSelect?: (agent: Agent) => void;\n onStart?: (agent: Agent) => void;\n onStop?: (agent: Agent) => void;\n onDisconnect?: (agent: Agent) => void;\n onSettings?: (agent: Agent) => void;\n onCreateNew?: () => void;\n isLoading?: boolean;\n selectedIds?: Set<string>;\n onToggleSelect?: (agentId: string) => void;\n onSelectAll?: () => void;\n onDeselectAll?: () => void;\n}\n\nconst platformLabels: Record<string, string> = {\n darwin: '๐ŸŽ macOS',\n linux: '๐Ÿง Linux',\n windows: '๐ŸชŸ Windows',\n};\n\n/**\n * Mobile card view for a single agent (used on small screens)\n */\nfunction AgentListCard({\n agent,\n onSelect,\n onStart,\n onStop,\n onDisconnect,\n onSettings,\n selected = false,\n onToggleSelect,\n}: {\n agent: Agent;\n onSelect?: (agent: Agent) => void;\n onStart?: (agent: Agent) => void;\n onStop?: (agent: Agent) => void;\n onDisconnect?: (agent: Agent) => void;\n onSettings?: (agent: Agent) => void;\n selected?: boolean;\n onToggleSelect?: () => void;\n}) {\n return (\n <div\n role=\"button\"\n tabIndex={0}\n onKeyDown={(e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n (() => onSelect?.(agent))();\n }\n }}\n className={`relative p-4 border border-border-default rounded-lg bg-bg-surface hover:bg-bg-sunken/50 cursor-pointer transition-colors ${\n selected ? 'ring-2 ring-action-primary-bg' : ''\n }`}\n onClick={() => onSelect?.(agent)}\n >\n <div className=\"flex items-start justify-between gap-3\">\n <div className=\"flex items-start gap-3 min-w-0 flex-1\">\n {onToggleSelect && (\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation();\n onToggleSelect();\n }}\n className={`size-5 min-w-[1.25rem] min-h-[1.25rem] rounded border transition-colors mt-0.5 ${\n selected\n ? 'bg-action-primary-bg border-action-primary-bg text-action-primary-text'\n : 'border-border-default hover:border-border-strong bg-bg-surface'\n } flex items-center justify-center flex-shrink-0`}\n >\n {selected && (\n <svg\n className=\"size-3\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n strokeWidth={3}\n >\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M5 13l4 4L19 7\" />\n </svg>\n )}\n </button>\n )}\n <Server className=\"size-5 text-text-secondary flex-shrink-0 mt-0.5\" />\n <div className=\"min-w-0 flex-1\">\n <p className=\"text-sm font-medium text-text-primary truncate\">{agent.name}</p>\n <p className=\"text-xs text-text-muted truncate\">\n {agent.hostname || agent.id.slice(0, 8)}\n </p>\n </div>\n </div>\n <AgentStatusBadge status={getAgentStatus(agent)} />\n </div>\n\n <div className=\"mt-3 flex flex-wrap items-center gap-x-4 gap-y-2 text-xs text-text-secondary\">\n <span>{platformLabels[agent.platform] || agent.platform || '-'}</span>\n <span>{agent.architecture || '-'}</span>\n <span className=\"flex items-center gap-1\">\n <Clock className=\"size-3\" />\n {formatLastHeartbeat(agent.lastHeartbeat)}\n </span>\n </div>\n\n <div className=\"mt-3 flex items-center gap-2 justify-end border-t border-border-subtle pt-3\">\n {agent.isActive && onStop && (\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation();\n onStop(agent);\n }}\n className=\"p-2 rounded hover:bg-bg-sunken transition-colors\"\n title=\"Stop\"\n >\n <Square className=\"size-4 text-text-secondary\" />\n </button>\n )}\n {!agent.isActive && onStart && (\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation();\n onStart(agent);\n }}\n className=\"p-2 rounded hover:bg-bg-sunken transition-colors\"\n title=\"Start\"\n >\n <Play className=\"size-4 text-text-secondary\" />\n </button>\n )}\n {onDisconnect &&\n (['ACTIVE', 'STOPPED', 'OFFLINE'] as AgentStatus[]).includes(getAgentStatus(agent)) && (\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation();\n onDisconnect(agent);\n }}\n className=\"p-2 rounded hover:bg-bg-sunken transition-colors\"\n title={getAgentStatus(agent) === 'OFFLINE' ? 'Clear Connection' : 'Disconnect'}\n aria-label={getAgentStatus(agent) === 'OFFLINE' ? 'Clear Connection' : 'Disconnect'}\n >\n <Link2Off className=\"size-4 text-action-danger-text\" />\n </button>\n )}\n {onSettings && (\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation();\n onSettings(agent);\n }}\n className=\"p-2 rounded hover:bg-bg-sunken transition-colors\"\n title=\"Settings\"\n >\n <Settings className=\"size-4 text-text-secondary\" />\n </button>\n )}\n </div>\n </div>\n );\n}\n\nfunction AgentRow({\n agent,\n onSelect,\n onStart,\n onStop,\n onDisconnect,\n onSettings,\n selected = false,\n onToggleSelect,\n}: {\n agent: Agent;\n onSelect?: (agent: Agent) => void;\n onStart?: (agent: Agent) => void;\n onStop?: (agent: Agent) => void;\n onDisconnect?: (agent: Agent) => void;\n onSettings?: (agent: Agent) => void;\n selected?: boolean;\n onToggleSelect?: () => void;\n}) {\n return (\n <tr\n className={`relative border-b border-border-subtle hover:bg-bg-sunken/50 cursor-pointer transition-colors ${\n selected ? 'bg-action-primary-bg/5' : ''\n }`}\n onClick={() => onSelect?.(agent)}\n >\n {onToggleSelect && (\n <td className=\"px-4 py-3 w-12\">\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation();\n onToggleSelect();\n }}\n className={`size-5 min-w-[1.25rem] min-h-[1.25rem] rounded border transition-colors ${\n selected\n ? 'bg-action-primary-bg border-action-primary-bg text-action-primary-text'\n : 'border-border-default hover:border-border-strong bg-bg-surface'\n } flex items-center justify-center`}\n >\n {selected && (\n <svg\n className=\"size-3\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n strokeWidth={3}\n >\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M5 13l4 4L19 7\" />\n </svg>\n )}\n </button>\n </td>\n )}\n <td className=\"px-4 py-3\">\n <div className=\"flex items-center gap-3 min-w-0\">\n <Server className=\"size-4 text-text-secondary shrink-0\" />\n <div className=\"min-w-0\">\n <p className=\"text-sm font-medium text-text-primary truncate\">{agent.name}</p>\n <p className=\"text-xs text-text-muted truncate\">\n {agent.hostname || agent.id.slice(0, 8)}\n </p>\n </div>\n </div>\n </td>\n <td className=\"px-4 py-3\">\n <AgentStatusBadge status={getAgentStatus(agent)} />\n </td>\n <td className=\"px-4 py-3\">\n <span className=\"text-sm text-text-secondary\">\n {platformLabels[agent.platform] || agent.platform || '-'}\n </span>\n </td>\n <td className=\"px-4 py-3\">\n <span className=\"text-sm text-text-secondary\">{agent.architecture || '-'}</span>\n </td>\n <td className=\"px-4 py-3\">\n <div className=\"flex items-center gap-1.5 text-xs text-text-muted\">\n <Clock className=\"size-3\" />\n {formatLastHeartbeat(agent.lastHeartbeat)}\n </div>\n </td>\n <td className=\"px-4 py-3\">\n <div className=\"flex items-center gap-1 justify-end\">\n {agent.isActive && onStop && (\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation();\n onStop(agent);\n }}\n className=\"p-1.5 rounded hover:bg-bg-sunken transition-colors\"\n title=\"Stop\"\n >\n <Square className=\"size-3.5 text-text-secondary\" />\n </button>\n )}\n {!agent.isActive && onStart && (\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation();\n onStart(agent);\n }}\n className=\"p-1.5 rounded hover:bg-bg-sunken transition-colors\"\n title=\"Start\"\n >\n <Play className=\"size-3.5 text-text-secondary\" />\n </button>\n )}\n {onDisconnect &&\n (['ACTIVE', 'STOPPED', 'OFFLINE'] as AgentStatus[]).includes(getAgentStatus(agent)) && (\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation();\n onDisconnect(agent);\n }}\n className=\"p-1.5 rounded hover:bg-bg-sunken transition-colors\"\n title={getAgentStatus(agent) === 'OFFLINE' ? 'Clear Connection' : 'Disconnect'}\n aria-label={getAgentStatus(agent) === 'OFFLINE' ? 'Clear Connection' : 'Disconnect'}\n >\n <Link2Off className=\"size-3.5 text-action-danger-text\" />\n </button>\n )}\n {onSettings && (\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation();\n onSettings(agent);\n }}\n className=\"p-1.5 rounded hover:bg-bg-sunken transition-colors\"\n title=\"Settings\"\n >\n <Settings className=\"size-3.5 text-text-secondary\" />\n </button>\n )}\n </div>\n </td>\n </tr>\n );\n}\n\nexport function AgentList({\n agents,\n onSelect,\n onStart,\n onStop,\n onDisconnect,\n onSettings,\n onCreateNew,\n isLoading,\n selectedIds,\n onToggleSelect,\n onSelectAll,\n onDeselectAll,\n}: AgentListProps) {\n const tr = useTr();\n const isAllSelected = selectedIds && agents.length > 0 && selectedIds.size === agents.length;\n if (!isLoading && agents.length === 0) {\n return (\n <EmptyState\n icon={<Server className=\"size-8 text-text-secondary\" />}\n title={tr('vibecontrols.agents.emptyTitle', 'No agents configured')}\n description={tr(\n 'vibecontrols.agents.emptyDescription',\n 'Agents are the bridge between VibeControls and your development environments. Install an agent to get started.'\n )}\n action={\n onCreateNew && (\n <button\n type=\"button\"\n onClick={onCreateNew}\n className=\"inline-flex items-center gap-2 px-4 py-2 text-sm font-medium text-action-primary-text bg-action-primary-bg rounded-md hover:bg-action-primary-bg-hover transition-colors\"\n >\n <Plus className=\"size-4\" />\n {tr('vibecontrols.agents.addAgent', 'Add Agent')}\n </button>\n )\n }\n />\n );\n }\n\n return (\n <>\n {/* Mobile: Card view */}\n <div className=\"sm:hidden space-y-3\">\n {/* Select All button for mobile */}\n {onToggleSelect && agents.length > 0 && (\n <div className=\"flex justify-end\">\n <button\n type=\"button\"\n onClick={() => (isAllSelected ? onDeselectAll?.() : onSelectAll?.())}\n className=\"text-xs text-text-secondary hover:text-text-primary transition-colors\"\n >\n {isAllSelected\n ? tr('vibecontrols.agents.deselectAll', 'Deselect All')\n : tr('vibecontrols.agents.selectAll', 'Select All')}\n </button>\n </div>\n )}\n {agents.map((agent) => (\n <AgentListCard\n key={agent.id}\n agent={agent}\n onSelect={onSelect}\n onStart={onStart}\n onStop={onStop}\n onDisconnect={onDisconnect}\n onSettings={onSettings}\n selected={selectedIds?.has(agent.id) ?? false}\n onToggleSelect={onToggleSelect ? () => onToggleSelect(agent.id) : undefined}\n />\n ))}\n </div>\n\n {/* Desktop: Table view */}\n <div className=\"hidden sm:block border border-border-default rounded-lg overflow-hidden\">\n <div className=\"overflow-x-auto\">\n <table className=\"w-full\">\n <thead>\n <tr className=\"bg-bg-sunken border-b border-border-default\">\n {onToggleSelect && (\n <th className=\"px-4 py-2.5 w-12\">\n <button\n type=\"button\"\n onClick={() => (isAllSelected ? onDeselectAll?.() : onSelectAll?.())}\n className={`size-5 min-w-[1.25rem] min-h-[1.25rem] rounded border transition-colors ${\n isAllSelected\n ? 'bg-action-primary-bg border-action-primary-bg text-action-primary-text'\n : 'border-border-default hover:border-border-strong bg-bg-surface'\n } flex items-center justify-center`}\n >\n {isAllSelected && (\n <svg\n className=\"size-3\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n strokeWidth={3}\n >\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M5 13l4 4L19 7\" />\n </svg>\n )}\n </button>\n </th>\n )}\n <th className=\"px-4 py-2.5 text-left text-xs font-medium text-text-secondary uppercase tracking-wider\">\n {tr('vibecontrols.agents.columnAgent', 'Agent')}\n </th>\n <th className=\"px-4 py-2.5 text-left text-xs font-medium text-text-secondary uppercase tracking-wider\">\n {tr('vibecontrols.agents.columnStatus', 'Status')}\n </th>\n <th className=\"px-4 py-2.5 text-left text-xs font-medium text-text-secondary uppercase tracking-wider\">\n {tr('vibecontrols.agents.columnPlatform', 'Platform')}\n </th>\n <th className=\"px-4 py-2.5 text-left text-xs font-medium text-text-secondary uppercase tracking-wider\">\n {tr('vibecontrols.agents.columnArch', 'Arch')}\n </th>\n <th className=\"px-4 py-2.5 text-left text-xs font-medium text-text-secondary uppercase tracking-wider\">\n {tr('vibecontrols.agents.columnLastHeartbeat', 'Last Heartbeat')}\n </th>\n <th className=\"px-4 py-2.5 text-right text-xs font-medium text-text-secondary uppercase tracking-wider\">\n {tr('vibecontrols.agents.columnActions', 'Actions')}\n </th>\n </tr>\n </thead>\n <tbody>\n {agents.map((agent) => (\n <AgentRow\n key={agent.id}\n agent={agent}\n onSelect={onSelect}\n onStart={onStart}\n onStop={onStop}\n onDisconnect={onDisconnect}\n onSettings={onSettings}\n selected={selectedIds?.has(agent.id) ?? false}\n onToggleSelect={onToggleSelect ? () => onToggleSelect(agent.id) : undefined}\n />\n ))}\n </tbody>\n </table>\n </div>\n </div>\n </>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"AgentList.js","names":[],"sources":["../../../src/components/agents/AgentList.tsx"],"sourcesContent":["import type { Agent, AgentStatus } from './types';\nimport { AgentStatusBadge } from './AgentStatusBadge';\nimport { EmptyState } from '../shared/EmptyState';\nimport { Server, Plus, Clock, Play, Square, Settings, Link2Off } from 'lucide-react';\nimport { useTr } from '../../shared/hooks/useTr';\nimport { getAgentStatus } from './agentStatus';\n\n/**\n * Format a timestamp as a relative time string (e.g. \"5m ago\", \"2h ago\")\n */\nfunction formatLastHeartbeat(timestamp: string | null | undefined): string {\n if (!timestamp) return 'Never';\n const diff = Date.now() - new Date(timestamp).getTime();\n if (diff < 60000) return 'Just now';\n if (diff < 3600000) return `${Math.floor(diff / 60000)}m ago`;\n if (diff < 86400000) return `${Math.floor(diff / 3600000)}h ago`;\n return `${Math.floor(diff / 86400000)}d ago`;\n}\n\ninterface AgentListProps {\n agents: Agent[];\n onSelect?: (agent: Agent) => void;\n onStart?: (agent: Agent) => void;\n onStop?: (agent: Agent) => void;\n onDisconnect?: (agent: Agent) => void;\n onSettings?: (agent: Agent) => void;\n onCreateNew?: () => void;\n isLoading?: boolean;\n selectedIds?: Set<string>;\n onToggleSelect?: (agentId: string) => void;\n onSelectAll?: () => void;\n onDeselectAll?: () => void;\n}\n\nconst platformLabels: Record<string, string> = {\n darwin: '๐ŸŽ macOS',\n linux: '๐Ÿง Linux',\n windows: '๐ŸชŸ Windows',\n};\n\n/**\n * Mobile card view for a single agent (used on small screens)\n */\nfunction AgentListCard({\n agent,\n onSelect,\n onStart,\n onStop,\n onDisconnect,\n onSettings,\n selected = false,\n onToggleSelect,\n}: {\n agent: Agent;\n onSelect?: (agent: Agent) => void;\n onStart?: (agent: Agent) => void;\n onStop?: (agent: Agent) => void;\n onDisconnect?: (agent: Agent) => void;\n onSettings?: (agent: Agent) => void;\n selected?: boolean;\n onToggleSelect?: () => void;\n}) {\n return (\n <div\n role=\"button\"\n tabIndex={0}\n onKeyDown={(e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n (() => onSelect?.(agent))();\n }\n }}\n className={`relative p-4 border border-border-default rounded-lg bg-bg-surface hover:bg-bg-sunken/50 cursor-pointer transition-colors ${\n selected ? 'ring-2 ring-action-primary-bg' : ''\n }`}\n onClick={() => onSelect?.(agent)}\n >\n <div className=\"flex items-start justify-between gap-3\">\n <div className=\"flex items-start gap-3 min-w-0 flex-1\">\n {onToggleSelect && (\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation();\n onToggleSelect();\n }}\n className={`size-5 min-w-[1.25rem] min-h-[1.25rem] rounded border transition-colors mt-0.5 ${\n selected\n ? 'bg-action-primary-bg border-action-primary-bg text-action-primary-text'\n : 'border-border-default hover:border-border-strong bg-bg-surface'\n } flex items-center justify-center flex-shrink-0`}\n >\n {selected && (\n <svg\n className=\"size-3\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n strokeWidth={3}\n >\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M5 13l4 4L19 7\" />\n </svg>\n )}\n </button>\n )}\n <Server className=\"size-5 text-text-secondary flex-shrink-0 mt-0.5\" />\n <div className=\"min-w-0 flex-1\">\n <p className=\"text-sm font-medium text-text-primary truncate\">{agent.name}</p>\n <p className=\"text-xs text-text-muted truncate\">\n {agent.hostname || agent.id.slice(0, 8)}\n </p>\n </div>\n </div>\n <AgentStatusBadge\n status={getAgentStatus(agent)}\n linkTo={`/vibecontrols/agents/${agent.id}?tab=connection`}\n onClick={(event) => event.stopPropagation()}\n />\n </div>\n\n <div className=\"mt-3 flex flex-wrap items-center gap-x-4 gap-y-2 text-xs text-text-secondary\">\n <span>{platformLabels[agent.platform] || agent.platform || '-'}</span>\n <span>{agent.architecture || '-'}</span>\n <span className=\"flex items-center gap-1\">\n <Clock className=\"size-3\" />\n {formatLastHeartbeat(agent.lastHeartbeat)}\n </span>\n </div>\n\n <div className=\"mt-3 flex items-center gap-2 justify-end border-t border-border-subtle pt-3\">\n {agent.isActive && onStop && (\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation();\n onStop(agent);\n }}\n className=\"p-2 rounded hover:bg-bg-sunken transition-colors\"\n title=\"Stop\"\n >\n <Square className=\"size-4 text-text-secondary\" />\n </button>\n )}\n {!agent.isActive && onStart && (\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation();\n onStart(agent);\n }}\n className=\"p-2 rounded hover:bg-bg-sunken transition-colors\"\n title=\"Start\"\n >\n <Play className=\"size-4 text-text-secondary\" />\n </button>\n )}\n {onDisconnect &&\n (['ACTIVE', 'STOPPED', 'OFFLINE'] as AgentStatus[]).includes(getAgentStatus(agent)) && (\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation();\n onDisconnect(agent);\n }}\n className=\"p-2 rounded hover:bg-bg-sunken transition-colors\"\n title={getAgentStatus(agent) === 'OFFLINE' ? 'Clear Connection' : 'Disconnect'}\n aria-label={getAgentStatus(agent) === 'OFFLINE' ? 'Clear Connection' : 'Disconnect'}\n >\n <Link2Off className=\"size-4 text-action-danger-text\" />\n </button>\n )}\n {onSettings && (\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation();\n onSettings(agent);\n }}\n className=\"p-2 rounded hover:bg-bg-sunken transition-colors\"\n title=\"Settings\"\n >\n <Settings className=\"size-4 text-text-secondary\" />\n </button>\n )}\n </div>\n </div>\n );\n}\n\nfunction AgentRow({\n agent,\n onSelect,\n onStart,\n onStop,\n onDisconnect,\n onSettings,\n selected = false,\n onToggleSelect,\n}: {\n agent: Agent;\n onSelect?: (agent: Agent) => void;\n onStart?: (agent: Agent) => void;\n onStop?: (agent: Agent) => void;\n onDisconnect?: (agent: Agent) => void;\n onSettings?: (agent: Agent) => void;\n selected?: boolean;\n onToggleSelect?: () => void;\n}) {\n return (\n <tr\n className={`relative border-b border-border-subtle hover:bg-bg-sunken/50 cursor-pointer transition-colors ${\n selected ? 'bg-action-primary-bg/5' : ''\n }`}\n onClick={() => onSelect?.(agent)}\n >\n {onToggleSelect && (\n <td className=\"px-4 py-3 w-12\">\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation();\n onToggleSelect();\n }}\n className={`size-5 min-w-[1.25rem] min-h-[1.25rem] rounded border transition-colors ${\n selected\n ? 'bg-action-primary-bg border-action-primary-bg text-action-primary-text'\n : 'border-border-default hover:border-border-strong bg-bg-surface'\n } flex items-center justify-center`}\n >\n {selected && (\n <svg\n className=\"size-3\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n strokeWidth={3}\n >\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M5 13l4 4L19 7\" />\n </svg>\n )}\n </button>\n </td>\n )}\n <td className=\"px-4 py-3\">\n <div className=\"flex items-center gap-3 min-w-0\">\n <Server className=\"size-4 text-text-secondary shrink-0\" />\n <div className=\"min-w-0\">\n <p className=\"text-sm font-medium text-text-primary truncate\">{agent.name}</p>\n <p className=\"text-xs text-text-muted truncate\">\n {agent.hostname || agent.id.slice(0, 8)}\n </p>\n </div>\n </div>\n </td>\n <td className=\"px-4 py-3\">\n <AgentStatusBadge\n status={getAgentStatus(agent)}\n linkTo={`/vibecontrols/agents/${agent.id}?tab=connection`}\n onClick={(event) => event.stopPropagation()}\n />\n </td>\n <td className=\"px-4 py-3\">\n <span className=\"text-sm text-text-secondary\">\n {platformLabels[agent.platform] || agent.platform || '-'}\n </span>\n </td>\n <td className=\"px-4 py-3\">\n <span className=\"text-sm text-text-secondary\">{agent.architecture || '-'}</span>\n </td>\n <td className=\"px-4 py-3\">\n <div className=\"flex items-center gap-1.5 text-xs text-text-muted\">\n <Clock className=\"size-3\" />\n {formatLastHeartbeat(agent.lastHeartbeat)}\n </div>\n </td>\n <td className=\"px-4 py-3\">\n <div className=\"flex items-center gap-1 justify-end\">\n {agent.isActive && onStop && (\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation();\n onStop(agent);\n }}\n className=\"p-1.5 rounded hover:bg-bg-sunken transition-colors\"\n title=\"Stop\"\n >\n <Square className=\"size-3.5 text-text-secondary\" />\n </button>\n )}\n {!agent.isActive && onStart && (\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation();\n onStart(agent);\n }}\n className=\"p-1.5 rounded hover:bg-bg-sunken transition-colors\"\n title=\"Start\"\n >\n <Play className=\"size-3.5 text-text-secondary\" />\n </button>\n )}\n {onDisconnect &&\n (['ACTIVE', 'STOPPED', 'OFFLINE'] as AgentStatus[]).includes(getAgentStatus(agent)) && (\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation();\n onDisconnect(agent);\n }}\n className=\"p-1.5 rounded hover:bg-bg-sunken transition-colors\"\n title={getAgentStatus(agent) === 'OFFLINE' ? 'Clear Connection' : 'Disconnect'}\n aria-label={getAgentStatus(agent) === 'OFFLINE' ? 'Clear Connection' : 'Disconnect'}\n >\n <Link2Off className=\"size-3.5 text-action-danger-text\" />\n </button>\n )}\n {onSettings && (\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation();\n onSettings(agent);\n }}\n className=\"p-1.5 rounded hover:bg-bg-sunken transition-colors\"\n title=\"Settings\"\n >\n <Settings className=\"size-3.5 text-text-secondary\" />\n </button>\n )}\n </div>\n </td>\n </tr>\n );\n}\n\nexport function AgentList({\n agents,\n onSelect,\n onStart,\n onStop,\n onDisconnect,\n onSettings,\n onCreateNew,\n isLoading,\n selectedIds,\n onToggleSelect,\n onSelectAll,\n onDeselectAll,\n}: AgentListProps) {\n const tr = useTr();\n const isAllSelected = selectedIds && agents.length > 0 && selectedIds.size === agents.length;\n if (!isLoading && agents.length === 0) {\n return (\n <EmptyState\n icon={<Server className=\"size-8 text-text-secondary\" />}\n title={tr('vibecontrols.agents.emptyTitle', 'No agents configured')}\n description={tr(\n 'vibecontrols.agents.emptyDescription',\n 'Agents are the bridge between VibeControls and your development environments. Install an agent to get started.'\n )}\n action={\n onCreateNew && (\n <button\n type=\"button\"\n onClick={onCreateNew}\n className=\"inline-flex items-center gap-2 px-4 py-2 text-sm font-medium text-action-primary-text bg-action-primary-bg rounded-md hover:bg-action-primary-bg-hover transition-colors\"\n >\n <Plus className=\"size-4\" />\n {tr('vibecontrols.agents.addAgent', 'Add Agent')}\n </button>\n )\n }\n />\n );\n }\n\n return (\n <>\n {/* Mobile: Card view */}\n <div className=\"sm:hidden space-y-3\">\n {/* Select All button for mobile */}\n {onToggleSelect && agents.length > 0 && (\n <div className=\"flex justify-end\">\n <button\n type=\"button\"\n onClick={() => (isAllSelected ? onDeselectAll?.() : onSelectAll?.())}\n className=\"text-xs text-text-secondary hover:text-text-primary transition-colors\"\n >\n {isAllSelected\n ? tr('vibecontrols.agents.deselectAll', 'Deselect All')\n : tr('vibecontrols.agents.selectAll', 'Select All')}\n </button>\n </div>\n )}\n {agents.map((agent) => (\n <AgentListCard\n key={agent.id}\n agent={agent}\n onSelect={onSelect}\n onStart={onStart}\n onStop={onStop}\n onDisconnect={onDisconnect}\n onSettings={onSettings}\n selected={selectedIds?.has(agent.id) ?? false}\n onToggleSelect={onToggleSelect ? () => onToggleSelect(agent.id) : undefined}\n />\n ))}\n </div>\n\n {/* Desktop: Table view */}\n <div className=\"hidden sm:block border border-border-default rounded-lg overflow-hidden\">\n <div className=\"overflow-x-auto\">\n <table className=\"w-full\">\n <thead>\n <tr className=\"bg-bg-sunken border-b border-border-default\">\n {onToggleSelect && (\n <th className=\"px-4 py-2.5 w-12\">\n <button\n type=\"button\"\n onClick={() => (isAllSelected ? onDeselectAll?.() : onSelectAll?.())}\n className={`size-5 min-w-[1.25rem] min-h-[1.25rem] rounded border transition-colors ${\n isAllSelected\n ? 'bg-action-primary-bg border-action-primary-bg text-action-primary-text'\n : 'border-border-default hover:border-border-strong bg-bg-surface'\n } flex items-center justify-center`}\n >\n {isAllSelected && (\n <svg\n className=\"size-3\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n strokeWidth={3}\n >\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M5 13l4 4L19 7\" />\n </svg>\n )}\n </button>\n </th>\n )}\n <th className=\"px-4 py-2.5 text-left text-xs font-medium text-text-secondary uppercase tracking-wider\">\n {tr('vibecontrols.agents.columnAgent', 'Agent')}\n </th>\n <th className=\"px-4 py-2.5 text-left text-xs font-medium text-text-secondary uppercase tracking-wider\">\n {tr('vibecontrols.agents.columnStatus', 'Status')}\n </th>\n <th className=\"px-4 py-2.5 text-left text-xs font-medium text-text-secondary uppercase tracking-wider\">\n {tr('vibecontrols.agents.columnPlatform', 'Platform')}\n </th>\n <th className=\"px-4 py-2.5 text-left text-xs font-medium text-text-secondary uppercase tracking-wider\">\n {tr('vibecontrols.agents.columnArch', 'Arch')}\n </th>\n <th className=\"px-4 py-2.5 text-left text-xs font-medium text-text-secondary uppercase tracking-wider\">\n {tr('vibecontrols.agents.columnLastHeartbeat', 'Last Heartbeat')}\n </th>\n <th className=\"px-4 py-2.5 text-right text-xs font-medium text-text-secondary uppercase tracking-wider\">\n {tr('vibecontrols.agents.columnActions', 'Actions')}\n </th>\n </tr>\n </thead>\n <tbody>\n {agents.map((agent) => (\n <AgentRow\n key={agent.id}\n agent={agent}\n onSelect={onSelect}\n onStart={onStart}\n onStop={onStop}\n onDisconnect={onDisconnect}\n onSettings={onSettings}\n selected={selectedIds?.has(agent.id) ?? false}\n onToggleSelect={onToggleSelect ? () => onToggleSelect(agent.id) : undefined}\n />\n ))}\n </tbody>\n </table>\n </div>\n </div>\n </>\n );\n}\n"],"mappings":"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"}
@@ -1,8 +1,8 @@
1
1
  import { useI18n as e } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
2
2
  import { Loader2 as t } from "lucide-react";
3
- import { jsx as n, jsxs as r } from "react/jsx-runtime";
3
+ import { Fragment as n, jsx as r, jsxs as i } from "react/jsx-runtime";
4
4
  //#region src/components/agents/AgentStatusBadge.tsx
5
- var i = {
5
+ var a = {
6
6
  REGISTERING: {
7
7
  label: "Registering",
8
8
  color: "bg-status-info-bg text-status-info-text border-status-info-border"
@@ -36,19 +36,33 @@ var i = {
36
36
  label: "Deleted",
37
37
  color: "bg-status-error-bg text-status-error-text border-status-error-border"
38
38
  }
39
- }, a = ({ status: a, className: o = "", reason: s }) => {
40
- let { t: c } = e(), l = i[a], u = s && (a === "DEGRADED" || a === "OFFLINE") ? `${l.label}: ${s}` : void 0;
41
- return /* @__PURE__ */ r("span", {
42
- title: u,
43
- "aria-label": u ?? l.label,
44
- className: `inline-flex items-center gap-1 px-2.5 py-0.5 rounded-full text-xs font-medium border transition-colors duration-150 ${l.color} ${o}`,
45
- children: [l.spinner && /* @__PURE__ */ n(t, {
46
- className: "size-3 animate-spin",
47
- "aria-hidden": "true"
48
- }), l.label]
39
+ }, o = "See connection log for details", s = ({ status: s, className: c = "", reason: l, linkTo: u, onClick: d }) => {
40
+ let { t: f } = e(), p = (e, t) => {
41
+ let n = f(e);
42
+ return n === e ? t : n;
43
+ }, m = a[s], h = !!u || !!d, g = [];
44
+ l && (s === "DEGRADED" || s === "OFFLINE") && g.push(`${m.label}: ${l}`), h && g.push(p("agents.statusBadge.connectionLogHint", o));
45
+ let _ = g.length > 0 ? g.join(" ยท ") : void 0, v = `inline-flex items-center gap-1 px-2.5 py-0.5 rounded-full text-xs font-medium border transition-colors duration-150 ${m.color} ${c}`, y = h ? " cursor-pointer hover:opacity-90 active:scale-95 transition-transform duration-100 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-strong" : "", b = /* @__PURE__ */ i(n, { children: [m.spinner && /* @__PURE__ */ r(t, {
46
+ className: "size-3 animate-spin",
47
+ "aria-hidden": "true"
48
+ }), m.label] });
49
+ return h ? /* @__PURE__ */ r("a", {
50
+ href: u ?? "#",
51
+ title: _,
52
+ "aria-label": _ ?? m.label,
53
+ className: v + y,
54
+ onClick: (e) => {
55
+ e.metaKey || e.ctrlKey || e.shiftKey || e.button !== 0 || d && d(e);
56
+ },
57
+ children: b
58
+ }) : /* @__PURE__ */ r("span", {
59
+ title: _,
60
+ "aria-label": _ ?? m.label,
61
+ className: v,
62
+ children: b
49
63
  });
50
64
  };
51
65
  //#endregion
52
- export { a as AgentStatusBadge };
66
+ export { s as AgentStatusBadge };
53
67
 
54
68
  //# sourceMappingURL=AgentStatusBadge.js.map
@@ -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 } from 'react';\nimport type { AgentStatus } from './types';\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\nconst statusConfig: Record<AgentStatus, { label: string; color: string; spinner?: boolean }> = {\n REGISTERING: {\n label: 'Registering',\n color: 'bg-status-info-bg text-status-info-text border-status-info-border',\n },\n CONNECTING: {\n label: 'Connecting',\n color: 'bg-status-info-bg text-status-info-text border-status-info-border',\n spinner: true,\n },\n ACTIVE: {\n label: 'Active',\n color: 'bg-status-success-bg text-status-success-text border-status-success-border',\n },\n STOPPED: {\n label: 'Stopped',\n color: 'bg-status-warning-bg text-status-warning-text border-status-warning-border',\n },\n IDLE: {\n label: 'Idle',\n color: 'bg-status-warning-bg text-status-warning-text border-status-warning-border',\n },\n DEGRADED: {\n label: 'Degraded',\n color: 'bg-status-warning-bg 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 text-status-error-text border-status-error-border',\n },\n};\n\n/**\n * AgentStatusBadge - Display agent status with appropriate styling\n */\nexport const AgentStatusBadge: FC<AgentStatusBadgeProps> = ({ status, className = '', reason }) => {\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 tooltip =\n reason && (status === 'DEGRADED' || status === 'OFFLINE')\n ? `${config.label}: ${reason}`\n : undefined;\n\n return (\n <span\n title={tooltip}\n aria-label={tooltip ?? config.label}\n className={`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 >\n {config.spinner && <Loader2 className=\"size-3 animate-spin\" aria-hidden=\"true\" />}\n {config.label}\n </span>\n );\n};\n"],"mappings":";;;;AAmBA,IAAM,IAAyF;CAC7F,aAAa;EACX,OAAO;EACP,OAAO;EACR;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,EAKY,KAA+C,EAAE,WAAQ,eAAY,IAAI,gBAAa;CACjG,IAAM,EAAE,SAAM,GAAS,EAKjB,IAAS,EAAa,IACtB,IACJ,MAAW,MAAW,cAAc,MAAW,aAC3C,GAAG,EAAO,MAAM,IAAI,MACpB,KAAA;AAEN,QACE,kBAAC,QAAD;EACE,OAAO;EACP,cAAY,KAAW,EAAO;EAC9B,WAAW,uHAAuH,EAAO,MAAM,GAAG;YAHpJ,CAKG,EAAO,WAAW,kBAAC,GAAD;GAAS,WAAU;GAAsB,eAAY;GAAS,CAAA,EAChF,EAAO,MACH"}
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';\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\nconst statusConfig: Record<AgentStatus, { label: string; color: string; spinner?: boolean }> = {\n REGISTERING: {\n label: 'Registering',\n color: 'bg-status-info-bg text-status-info-text border-status-info-border',\n },\n CONNECTING: {\n label: 'Connecting',\n color: 'bg-status-info-bg text-status-info-text border-status-info-border',\n spinner: true,\n },\n ACTIVE: {\n label: 'Active',\n color: 'bg-status-success-bg text-status-success-text border-status-success-border',\n },\n STOPPED: {\n label: 'Stopped',\n color: 'bg-status-warning-bg text-status-warning-text border-status-warning-border',\n },\n IDLE: {\n label: 'Idle',\n color: 'bg-status-warning-bg text-status-warning-text border-status-warning-border',\n },\n DEGRADED: {\n label: 'Degraded',\n color: 'bg-status-warning-bg 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 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 const tooltipParts: string[] = [];\n 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 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 active:scale-95 transition-transform duration-100 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-strong'\n : '';\n\n const content = (\n <>\n {config.spinner && <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":";;;;AAsCA,IAAM,IAAyF;CAC7F,aAAa;EACX,OAAO;EACP,OAAO;EACR;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,GAC3C,IAAyB,EAAE;AAIjC,CAHI,MAAW,MAAW,cAAc,MAAW,cACjD,EAAa,KAAK,GAAG,EAAO,MAAM,IAAI,IAAS,EAE7C,KACF,EAAa,KAAK,EAAG,wCAAwC,EAAoB,CAAC;CAEpF,IAAM,IAAU,EAAa,SAAS,IAAI,EAAa,KAAK,MAAM,GAAG,KAAA,GAE/D,IAAgB,uHAAuH,EAAO,MAAM,GAAG,KACvJ,IAAuB,IACzB,wKACA,IAEE,IACJ,kBAAA,GAAA,EAAA,UAAA,CACG,EAAO,WAAW,kBAAC,GAAD;EAAS,WAAU;EAAsB,eAAY;EAAS,CAAA,EAChF,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"}