@burdenoff/microfe-vibecontrols 2026.623.1 → 2026.623.2

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.
Files changed (293) hide show
  1. package/dist/components/actions/ActionCard.js +2 -2
  2. package/dist/components/actions/ActionCard.js.map +1 -1
  3. package/dist/components/actions/ExecutionHistory.js +1 -1
  4. package/dist/components/actions/ExecutionHistory.js.map +1 -1
  5. package/dist/components/agent-manager/AgentManagerAdminBar.js +1 -1
  6. package/dist/components/agent-manager/AgentManagerAdminBar.js.map +1 -1
  7. package/dist/components/agent-manager/AgentManagerChatPane.js +2 -2
  8. package/dist/components/agent-manager/AgentManagerChatPane.js.map +1 -1
  9. package/dist/components/agent-manager/AgentManagerInputArea.js +4 -4
  10. package/dist/components/agent-manager/AgentManagerInputArea.js.map +1 -1
  11. package/dist/components/agent-manager/AgentManagerLayout.js +1 -1
  12. package/dist/components/agent-manager/AgentManagerLayout.js.map +1 -1
  13. package/dist/components/agent-manager/AgentManagerMessageItem.js +1 -1
  14. package/dist/components/agent-manager/AgentManagerMessageItem.js.map +1 -1
  15. package/dist/components/agent-manager/AgentManagerSidebar.js +5 -5
  16. package/dist/components/agent-manager/AgentManagerSidebar.js.map +1 -1
  17. package/dist/components/agent-manager/AgentManagerTab.js +1 -1
  18. package/dist/components/agent-manager/AgentManagerTab.js.map +1 -1
  19. package/dist/components/agent-manager/AgentManagerTabBar.js +1 -1
  20. package/dist/components/agent-manager/AgentManagerTabBar.js.map +1 -1
  21. package/dist/components/agent-manager/BridleConfigPanel.js +23 -23
  22. package/dist/components/agent-manager/BridleConfigPanel.js.map +1 -1
  23. package/dist/components/agent-manager/PluginInstallModal.js +1 -1
  24. package/dist/components/agent-manager/PluginInstallModal.js.map +1 -1
  25. package/dist/components/agent-manager/ProfilesPanel.js +5 -5
  26. package/dist/components/agent-manager/ProfilesPanel.js.map +1 -1
  27. package/dist/components/agent-manager/controls/AutonomySelector.js +1 -1
  28. package/dist/components/agent-manager/controls/AutonomySelector.js.map +1 -1
  29. package/dist/components/agent-manager/controls/SdkSelector.js +1 -1
  30. package/dist/components/agent-manager/controls/SdkSelector.js.map +1 -1
  31. package/dist/components/agent-manager/controls/VoiceToggle.js +1 -1
  32. package/dist/components/agent-manager/controls/VoiceToggle.js.map +1 -1
  33. package/dist/components/agent-manager/detail/SessionConfigPanel.js +6 -6
  34. package/dist/components/agent-manager/detail/SessionConfigPanel.js.map +1 -1
  35. package/dist/components/agent-manager/detail/SessionLogsPanel.js +5 -5
  36. package/dist/components/agent-manager/detail/SessionLogsPanel.js.map +1 -1
  37. package/dist/components/agent-manager/detail/SessionToolsPanel.js +1 -1
  38. package/dist/components/agent-manager/detail/SessionToolsPanel.js.map +1 -1
  39. package/dist/components/agent-manager/dialogs/NewSessionDialog.js +2 -2
  40. package/dist/components/agent-manager/dialogs/NewSessionDialog.js.map +1 -1
  41. package/dist/components/agent-manager/dialogs/RenameSessionDialog.js +1 -1
  42. package/dist/components/agent-manager/dialogs/RenameSessionDialog.js.map +1 -1
  43. package/dist/components/agents/AgentCard.js +14 -11
  44. package/dist/components/agents/AgentCard.js.map +1 -1
  45. package/dist/components/ai/AIContextsTab.js +1 -1
  46. package/dist/components/ai/AIContextsTab.js.map +1 -1
  47. package/dist/components/ai/AIOverviewTab.js +4 -4
  48. package/dist/components/ai/AIOverviewTab.js.map +1 -1
  49. package/dist/components/ai/AIPlanPanel.js +4 -4
  50. package/dist/components/ai/AIPlanPanel.js.map +1 -1
  51. package/dist/components/ai/AIPlaygroundTab.js +1 -1
  52. package/dist/components/ai/AIPlaygroundTab.js.map +1 -1
  53. package/dist/components/ai/AIPromptsTab.js +2 -2
  54. package/dist/components/ai/AIPromptsTab.js.map +1 -1
  55. package/dist/components/ai/AIProviderCredentialsCard.js +4 -4
  56. package/dist/components/ai/AIProviderCredentialsCard.js.map +1 -1
  57. package/dist/components/ai/AIQueueTab.js +4 -4
  58. package/dist/components/ai/AIQueueTab.js.map +1 -1
  59. package/dist/components/ai/AISessionsTab.js +4 -4
  60. package/dist/components/ai/AISessionsTab.js.map +1 -1
  61. package/dist/components/ai/AITasksTab.js +1 -1
  62. package/dist/components/ai/AITasksTab.js.map +1 -1
  63. package/dist/components/ai/ClaudeCredentialsCard.js +3 -3
  64. package/dist/components/ai/ClaudeCredentialsCard.js.map +1 -1
  65. package/dist/components/ai/PluginHarnessPicker.js +3 -3
  66. package/dist/components/ai/PluginHarnessPicker.js.map +1 -1
  67. package/dist/components/analytics/AnalyticsBreakdownChart.js +8 -8
  68. package/dist/components/analytics/AnalyticsBreakdownChart.js.map +1 -1
  69. package/dist/components/analytics/AnalyticsTimeSeriesChart.js +5 -5
  70. package/dist/components/analytics/AnalyticsTimeSeriesChart.js.map +1 -1
  71. package/dist/components/audit/AuditDeleteDialog.js +2 -2
  72. package/dist/components/audit/AuditDeleteDialog.js.map +1 -1
  73. package/dist/components/audit/AuditExportButton.js +5 -5
  74. package/dist/components/audit/AuditExportButton.js.map +1 -1
  75. package/dist/components/audit/AuditLogDetail.js +9 -9
  76. package/dist/components/audit/AuditLogDetail.js.map +1 -1
  77. package/dist/components/audit/AuditLogFilters.js +1 -1
  78. package/dist/components/audit/AuditLogFilters.js.map +1 -1
  79. package/dist/components/audit/AuditLogStats.js +1 -1
  80. package/dist/components/audit/AuditLogStats.js.map +1 -1
  81. package/dist/components/audit/AuditLogTable.js +28 -28
  82. package/dist/components/audit/AuditLogTable.js.map +1 -1
  83. package/dist/components/catalog/CatalogCard.js +3 -3
  84. package/dist/components/catalog/CatalogCard.js.map +1 -1
  85. package/dist/components/catalog/CatalogForm.js +1 -1
  86. package/dist/components/catalog/CatalogForm.js.map +1 -1
  87. package/dist/components/common/ActionAgentTargetNotice.js +6 -6
  88. package/dist/components/common/ActionAgentTargetNotice.js.map +1 -1
  89. package/dist/components/common/CommandEditor.js +1 -1
  90. package/dist/components/common/CommandEditor.js.map +1 -1
  91. package/dist/components/docs/DocsPageTree.js +1 -1
  92. package/dist/components/docs/DocsPageTree.js.map +1 -1
  93. package/dist/components/docs/DocsPreviewPanel.js +1 -1
  94. package/dist/components/docs/DocsPreviewPanel.js.map +1 -1
  95. package/dist/components/docs/DocsRevisionHistory.js +4 -4
  96. package/dist/components/docs/DocsRevisionHistory.js.map +1 -1
  97. package/dist/components/docs/DocsSettingsPanel.js +7 -7
  98. package/dist/components/docs/DocsSettingsPanel.js.map +1 -1
  99. package/dist/components/docs/DocsTabPanel.js +1 -1
  100. package/dist/components/docs/DocsTabPanel.js.map +1 -1
  101. package/dist/components/graph/ConnectionPermissionsDialog.js +2 -2
  102. package/dist/components/graph/ConnectionPermissionsDialog.js.map +1 -1
  103. package/dist/components/graph/MeshDeployWizard.js +3 -3
  104. package/dist/components/graph/MeshDeployWizard.js.map +1 -1
  105. package/dist/components/graph/MeshRequestPanel.js +2 -2
  106. package/dist/components/graph/MeshRequestPanel.js.map +1 -1
  107. package/dist/components/idp-components/ComponentCard.js +5 -5
  108. package/dist/components/idp-components/ComponentCard.js.map +1 -1
  109. package/dist/components/idp-components/ComponentForm.js +1 -1
  110. package/dist/components/idp-components/ComponentForm.js.map +1 -1
  111. package/dist/components/idp-components/DependencyGraphTab.js +7 -7
  112. package/dist/components/idp-components/DependencyGraphTab.js.map +1 -1
  113. package/dist/components/logs/LogViewer.js +14 -14
  114. package/dist/components/logs/LogViewer.js.map +1 -1
  115. package/dist/components/notes/NoteCard.js +1 -1
  116. package/dist/components/notes/NoteCard.js.map +1 -1
  117. package/dist/components/notes/NoteEditor.js +2 -2
  118. package/dist/components/notes/NoteEditor.js.map +1 -1
  119. package/dist/components/notes/NoteList.js +1 -1
  120. package/dist/components/notes/NoteList.js.map +1 -1
  121. package/dist/components/notes/NotesTabPanel.js +1 -1
  122. package/dist/components/notes/NotesTabPanel.js.map +1 -1
  123. package/dist/components/scorecard/ScorecardEmptyState.js +1 -1
  124. package/dist/components/scorecard/ScorecardEmptyState.js.map +1 -1
  125. package/dist/components/scorecard/ScorecardPanel.js +3 -3
  126. package/dist/components/scorecard/ScorecardPanel.js.map +1 -1
  127. package/dist/components/security/SecurityScanList.js +11 -11
  128. package/dist/components/security/SecurityScanList.js.map +1 -1
  129. package/dist/components/sessions/SessionCard.js +26 -19
  130. package/dist/components/sessions/SessionCard.js.map +1 -1
  131. package/dist/components/sessions/SessionStatusBadge.js +7 -7
  132. package/dist/components/sessions/SessionStatusBadge.js.map +1 -1
  133. package/dist/components/shared/AgentSelector.js +4 -4
  134. package/dist/components/shared/AgentSelector.js.map +1 -1
  135. package/dist/components/shared/AgentSetupBanner.js +3 -3
  136. package/dist/components/shared/AgentSetupBanner.js.map +1 -1
  137. package/dist/components/shared/Card.js +4 -4
  138. package/dist/components/shared/Card.js.map +1 -1
  139. package/dist/components/shared/ErrorState.js +15 -9
  140. package/dist/components/shared/ErrorState.js.map +1 -1
  141. package/dist/components/shared/FilterDropdown.js +2 -2
  142. package/dist/components/shared/FilterDropdown.js.map +1 -1
  143. package/dist/components/shared/PageSkeleton.js +6 -6
  144. package/dist/components/shared/PageSkeleton.js.map +1 -1
  145. package/dist/components/shared/SearchInput.js +1 -1
  146. package/dist/components/shared/SearchInput.js.map +1 -1
  147. package/dist/components/shared/StatCard.js +7 -7
  148. package/dist/components/shared/StatCard.js.map +1 -1
  149. package/dist/components/templates/PublishTemplateForm.js +2 -2
  150. package/dist/components/templates/PublishTemplateForm.js.map +1 -1
  151. package/dist/components/templates/TemplateCard.js +5 -5
  152. package/dist/components/templates/TemplateCard.js.map +1 -1
  153. package/dist/components/templates/TemplateWizard.js +2 -2
  154. package/dist/components/templates/TemplateWizard.js.map +1 -1
  155. package/dist/components/tunnels/DomainVerificationBadge.js +4 -4
  156. package/dist/components/tunnels/DomainVerificationBadge.js.map +1 -1
  157. package/dist/components/tunnels/ProviderBadge.js +1 -1
  158. package/dist/components/tunnels/ProviderBadge.js.map +1 -1
  159. package/dist/components/tunnels/TunnelCard.js +16 -16
  160. package/dist/components/tunnels/TunnelCard.js.map +1 -1
  161. package/dist/components/tunnels/TunnelCreateWizard.js +1 -1
  162. package/dist/components/tunnels/TunnelCreateWizard.js.map +1 -1
  163. package/dist/components/tunnels/TunnelPublicAccessPanel.js +1 -1
  164. package/dist/components/tunnels/TunnelPublicAccessPanel.js.map +1 -1
  165. package/dist/components/tunnels/TunnelStatusBadge.js +4 -4
  166. package/dist/components/tunnels/TunnelStatusBadge.js.map +1 -1
  167. package/dist/components/vibecalendar/LogsDrawer.js +2 -2
  168. package/dist/components/vibecalendar/LogsDrawer.js.map +1 -1
  169. package/dist/components/vibecalendar/ScheduleTaskModal.js +5 -5
  170. package/dist/components/vibecalendar/ScheduleTaskModal.js.map +1 -1
  171. package/dist/components/vibecalendar/VibeCalAgendaView.js +2 -2
  172. package/dist/components/vibecalendar/VibeCalAgendaView.js.map +1 -1
  173. package/dist/components/vibecalendar/VibeCalDayView.js +3 -3
  174. package/dist/components/vibecalendar/VibeCalDayView.js.map +1 -1
  175. package/dist/components/vibecalendar/VibeCalMonthView.js +1 -1
  176. package/dist/components/vibecalendar/VibeCalMonthView.js.map +1 -1
  177. package/dist/components/vibecalendar/VibeCalWeekView.js +4 -4
  178. package/dist/components/vibecalendar/VibeCalWeekView.js.map +1 -1
  179. package/dist/components/vibecalendar/VibeCalendarHeader.js +4 -4
  180. package/dist/components/vibecalendar/VibeCalendarHeader.js.map +1 -1
  181. package/dist/components/vibecalendar/VibeTaskBlock.js +2 -2
  182. package/dist/components/vibecalendar/VibeTaskBlock.js.map +1 -1
  183. package/dist/components/vibecalendar/types.js +6 -6
  184. package/dist/components/vibecalendar/types.js.map +1 -1
  185. package/dist/components/vibedeck/VibeDeckButtonForm.js +9 -9
  186. package/dist/components/vibedeck/VibeDeckButtonForm.js.map +1 -1
  187. package/dist/components/vibedeck/VibeDeckButtonTile.js +6 -6
  188. package/dist/components/vibedeck/VibeDeckButtonTile.js.map +1 -1
  189. package/dist/components/vibedeck/VibeDeckExecutionLog.js +10 -10
  190. package/dist/components/vibedeck/VibeDeckExecutionLog.js.map +1 -1
  191. package/dist/components/vibedeck/VibeDeckForm.js +8 -8
  192. package/dist/components/vibedeck/VibeDeckForm.js.map +1 -1
  193. package/dist/components/vibedeck/VibeDeckGrid.js +2 -2
  194. package/dist/components/vibedeck/VibeDeckGrid.js.map +1 -1
  195. package/dist/components/vibedeck/VibeDeckPublicLinkPanel.js +2 -2
  196. package/dist/components/vibedeck/VibeDeckPublicLinkPanel.js.map +1 -1
  197. package/dist/components/vibedeck/VibeDeckSwitcher.js +8 -8
  198. package/dist/components/vibedeck/VibeDeckSwitcher.js.map +1 -1
  199. package/dist/components/vibes/VibeCard.js +22 -18
  200. package/dist/components/vibes/VibeCard.js.map +1 -1
  201. package/dist/components/vibes/VibeList.js +16 -12
  202. package/dist/components/vibes/VibeList.js.map +1 -1
  203. package/dist/pages/ActionsPage.js +4 -3
  204. package/dist/pages/ActionsPage.js.map +1 -1
  205. package/dist/pages/AgentDetailsPage.js +9 -9
  206. package/dist/pages/AgentDetailsPage.js.map +1 -1
  207. package/dist/pages/AgentsLandingPage.js +4 -4
  208. package/dist/pages/AgentsLandingPage.js.map +1 -1
  209. package/dist/pages/AgentsPage.js +14 -14
  210. package/dist/pages/AgentsPage.js.map +1 -1
  211. package/dist/pages/AnalyticsPage.js +8 -8
  212. package/dist/pages/AnalyticsPage.js.map +1 -1
  213. package/dist/pages/CatalogDetailPage.js +11 -11
  214. package/dist/pages/CatalogDetailPage.js.map +1 -1
  215. package/dist/pages/CatalogPage.js +12 -9
  216. package/dist/pages/CatalogPage.js.map +1 -1
  217. package/dist/pages/ComponentDetailPage.js +7 -7
  218. package/dist/pages/ComponentDetailPage.js.map +1 -1
  219. package/dist/pages/ComponentsPage.js +12 -9
  220. package/dist/pages/ComponentsPage.js.map +1 -1
  221. package/dist/pages/EnvTopologyPage.js +2 -2
  222. package/dist/pages/EnvTopologyPage.js.map +1 -1
  223. package/dist/pages/GettingStartedPage.js +12 -12
  224. package/dist/pages/GettingStartedPage.js.map +1 -1
  225. package/dist/pages/HealthPage.js +12 -12
  226. package/dist/pages/HealthPage.js.map +1 -1
  227. package/dist/pages/LogsPage.js +4 -4
  228. package/dist/pages/LogsPage.js.map +1 -1
  229. package/dist/pages/NoteDetailsPage.js +5 -5
  230. package/dist/pages/NoteDetailsPage.js.map +1 -1
  231. package/dist/pages/OverviewPage.js +74 -41
  232. package/dist/pages/OverviewPage.js.map +1 -1
  233. package/dist/pages/PluginsCatalogPage.js +6 -3
  234. package/dist/pages/PluginsCatalogPage.js.map +1 -1
  235. package/dist/pages/ScheduledJobDetailPage.js +7 -7
  236. package/dist/pages/ScheduledJobDetailPage.js.map +1 -1
  237. package/dist/pages/SessionDetailsPage.js +21 -21
  238. package/dist/pages/SessionDetailsPage.js.map +1 -1
  239. package/dist/pages/SessionsPage.js +20 -20
  240. package/dist/pages/SessionsPage.js.map +1 -1
  241. package/dist/pages/SettingsPage.js +64 -52
  242. package/dist/pages/SettingsPage.js.map +1 -1
  243. package/dist/pages/SharedSessionPage.js +21 -21
  244. package/dist/pages/SharedSessionPage.js.map +1 -1
  245. package/dist/pages/SharedVibeDeckPage.js +5 -5
  246. package/dist/pages/SharedVibeDeckPage.js.map +1 -1
  247. package/dist/pages/TargetDetailsPage.js +56 -65
  248. package/dist/pages/TargetDetailsPage.js.map +1 -1
  249. package/dist/pages/TargetsPage.js +4 -4
  250. package/dist/pages/TargetsPage.js.map +1 -1
  251. package/dist/pages/TemplateDetailPage.js +10 -10
  252. package/dist/pages/TemplateDetailPage.js.map +1 -1
  253. package/dist/pages/TemplatesPage.js +6 -5
  254. package/dist/pages/TemplatesPage.js.map +1 -1
  255. package/dist/pages/VibeAuditPage.js +3 -3
  256. package/dist/pages/VibeAuditPage.js.map +1 -1
  257. package/dist/pages/VibeCalendarPage.js +3 -3
  258. package/dist/pages/VibeCalendarPage.js.map +1 -1
  259. package/dist/pages/VibeDeckPage.js +3 -3
  260. package/dist/pages/VibeDeckPage.js.map +1 -1
  261. package/dist/pages/VibeDetailsPage.js +15 -15
  262. package/dist/pages/VibeDetailsPage.js.map +1 -1
  263. package/dist/pages/VibeTaskDetailPage.js +5 -5
  264. package/dist/pages/VibeTaskDetailPage.js.map +1 -1
  265. package/dist/pages/VibesPage.js +18 -18
  266. package/dist/pages/VibesPage.js.map +1 -1
  267. package/dist/pages/WebhooksPage.js +11 -11
  268. package/dist/pages/WebhooksPage.js.map +1 -1
  269. package/dist/pages/ai/AIBookmarksPage.js +1 -1
  270. package/dist/pages/ai/AIBookmarksPage.js.map +1 -1
  271. package/dist/pages/ai/AISessionDetailPage.js +6 -6
  272. package/dist/pages/ai/AISessionDetailPage.js.map +1 -1
  273. package/dist/pages/tunnels/AuditTab.js +1 -1
  274. package/dist/pages/tunnels/AuditTab.js.map +1 -1
  275. package/dist/pages/tunnels/DomainsTab.js +1 -1
  276. package/dist/pages/tunnels/DomainsTab.js.map +1 -1
  277. package/dist/pages/tunnels/ProvidersTab.js +1 -1
  278. package/dist/pages/tunnels/ProvidersTab.js.map +1 -1
  279. package/dist/pages/tunnels/SessionsTab.js +1 -1
  280. package/dist/pages/tunnels/SessionsTab.js.map +1 -1
  281. package/dist/pages/tunnels/TunnelDetailsPage.js +3 -3
  282. package/dist/pages/tunnels/TunnelDetailsPage.js.map +1 -1
  283. package/dist/pages/tunnels/UsageTab.js +1 -1
  284. package/dist/pages/tunnels/UsageTab.js.map +1 -1
  285. package/dist/pages/tunnels/tabs/TunnelDomainsTab.js +16 -16
  286. package/dist/pages/tunnels/tabs/TunnelDomainsTab.js.map +1 -1
  287. package/dist/pages/tunnels/tabs/TunnelOverviewTab.js +1 -1
  288. package/dist/pages/tunnels/tabs/TunnelOverviewTab.js.map +1 -1
  289. package/dist/pages/tunnels/tabs/TunnelSessionsTab.js +52 -49
  290. package/dist/pages/tunnels/tabs/TunnelSessionsTab.js.map +1 -1
  291. package/dist/pages/tunnels/tabs/TunnelSettingsTab.js +9 -9
  292. package/dist/pages/tunnels/tabs/TunnelSettingsTab.js.map +1 -1
  293. package/package.json +1 -1
@@ -5,15 +5,15 @@ import { Fragment as l, jsx as u, jsxs as d } from "react/jsx-runtime";
5
5
  //#region src/components/security/SecurityScanList.tsx
6
6
  var f = {
7
7
  queued: "bg-bg-sunken text-text-secondary",
8
- running: "bg-status-info-bg/20 text-status-info-text",
9
- succeeded: "bg-status-success-bg/20 text-status-success-text",
10
- failed: "bg-status-error-bg/20 text-status-error-text",
11
- errored: "bg-status-error-bg/20 text-status-error-text",
8
+ running: "bg-status-info-bg-subtle text-status-info-text",
9
+ succeeded: "bg-status-success-bg-subtle text-status-success-text",
10
+ failed: "bg-status-error-bg-subtle text-status-error-text",
11
+ errored: "bg-status-error-bg-subtle text-status-error-text",
12
12
  cancelled: "bg-bg-sunken text-text-tertiary"
13
13
  }, p = {
14
- pass: "bg-status-success-bg/20 text-status-success-text",
15
- warn: "bg-status-warning-bg/20 text-status-warning-text",
16
- fail: "bg-status-error-bg/20 text-status-error-text",
14
+ pass: "bg-status-success-bg-subtle text-status-success-text",
15
+ warn: "bg-status-warning-bg-subtle text-status-warning-text",
16
+ fail: "bg-status-error-bg-subtle text-status-error-text",
17
17
  skipped: "bg-bg-sunken text-text-tertiary"
18
18
  };
19
19
  function m(e) {
@@ -70,17 +70,17 @@ function v({ run: e }) {
70
70
  className: "flex items-center gap-1 text-xs",
71
71
  children: [
72
72
  t.critical > 0 && /* @__PURE__ */ d("span", {
73
- className: "inline-flex items-center gap-0.5 rounded px-1.5 py-0.5 bg-status-error-bg/20 text-status-error-text font-mono",
73
+ className: "inline-flex items-center gap-0.5 rounded px-1.5 py-0.5 bg-status-error-bg-subtle text-status-error-text font-mono",
74
74
  title: `${t.critical} critical`,
75
75
  children: ["C ", t.critical]
76
76
  }),
77
77
  t.high > 0 && /* @__PURE__ */ d("span", {
78
- className: "inline-flex items-center gap-0.5 rounded px-1.5 py-0.5 bg-status-warning-bg/20 text-status-warning-text font-mono",
78
+ className: "inline-flex items-center gap-0.5 rounded px-1.5 py-0.5 bg-status-warning-bg-subtle text-status-warning-text font-mono",
79
79
  title: `${t.high} high`,
80
80
  children: ["H ", t.high]
81
81
  }),
82
82
  t.medium > 0 && /* @__PURE__ */ d("span", {
83
- className: "inline-flex items-center gap-0.5 rounded px-1.5 py-0.5 bg-status-warning-bg/10 text-status-warning-text/80 font-mono",
83
+ className: "inline-flex items-center gap-0.5 rounded px-1.5 py-0.5 bg-status-warning-bg-subtle text-status-warning-text/80 font-mono",
84
84
  title: `${t.medium} medium`,
85
85
  children: ["M ", t.medium]
86
86
  }),
@@ -165,7 +165,7 @@ function b({ vibeId: r, agentId: i, profile: a, limit: o = 20 }) {
165
165
  })]
166
166
  }),
167
167
  x && /* @__PURE__ */ d("div", {
168
- className: "m-4 flex items-start gap-2 rounded-md border border-status-error-border bg-status-error-bg/10 p-3 text-sm",
168
+ className: "m-4 flex items-start gap-2 rounded-md border border-status-error-border bg-status-error-bg-subtle/10 p-3 text-sm",
169
169
  children: [/* @__PURE__ */ u(n, { className: "size-4 text-status-error-text mt-0.5 shrink-0" }), /* @__PURE__ */ d("div", {
170
170
  className: "text-text-primary",
171
171
  children: [/* @__PURE__ */ u("p", {
@@ -1 +1 @@
1
- {"version":3,"file":"SecurityScanList.js","names":[],"sources":["../../../src/components/security/SecurityScanList.tsx"],"sourcesContent":["/**\n * Recent scan runs list for a vibe, read directly from the agent over\n * the tunnel. No backend GraphQL — see `./agentClient.ts` for the\n * REST surface and `./hooks.ts` for the React Query wrappers.\n *\n * The agent doesn't expose a list-runs endpoint; we derive distinct\n * run ids from the findings cache (see `listRecentScanRuns`). When a\n * vibe has zero findings the list will be empty even if scans have\n * been queued — that's correct: an in-flight queued/running scan\n * doesn't yet have any findings, and the empty state already nudges\n * the user toward kicking off a scan.\n */\nimport { useMemo, type ReactElement } from 'react';\nimport {\n AlertCircle,\n CheckCircle2,\n Clock,\n Loader2,\n MinusCircle,\n ShieldAlert,\n XCircle,\n} from 'lucide-react';\n\nimport { useSecurityRecentScansQuery, type UseSecurityAgentContext } from './hooks';\nimport type { AgentScanConclusion, AgentScanStatus, SecurityScanRun } from './agentClient';\n\ninterface Props extends UseSecurityAgentContext {\n vibeId: string;\n /** Max rows to surface. Defaults to 20. */\n limit?: number;\n}\n\nconst STATUS_BADGE: Record<AgentScanStatus, string> = {\n queued: 'bg-bg-sunken text-text-secondary',\n running: 'bg-status-info-bg/20 text-status-info-text',\n succeeded: 'bg-status-success-bg/20 text-status-success-text',\n failed: 'bg-status-error-bg/20 text-status-error-text',\n errored: 'bg-status-error-bg/20 text-status-error-text',\n cancelled: 'bg-bg-sunken text-text-tertiary',\n};\n\nconst CONCLUSION_BADGE: Record<AgentScanConclusion, string> = {\n pass: 'bg-status-success-bg/20 text-status-success-text',\n warn: 'bg-status-warning-bg/20 text-status-warning-text',\n fail: 'bg-status-error-bg/20 text-status-error-text',\n skipped: 'bg-bg-sunken text-text-tertiary',\n};\n\nfunction statusIcon(status: AgentScanStatus): ReactElement {\n switch (status) {\n case 'queued':\n return <Clock className=\"size-3.5\" aria-hidden=\"true\" />;\n case 'running':\n return <Loader2 className=\"size-3.5 animate-spin\" aria-hidden=\"true\" />;\n case 'succeeded':\n return <CheckCircle2 className=\"size-3.5\" aria-hidden=\"true\" />;\n case 'failed':\n case 'errored':\n return <XCircle className=\"size-3.5\" aria-hidden=\"true\" />;\n case 'cancelled':\n return <MinusCircle className=\"size-3.5\" aria-hidden=\"true\" />;\n }\n}\n\nfunction relativeTime(epochMs?: number): string {\n if (!epochMs) return '—';\n const diff = Date.now() - epochMs;\n const sec = Math.round(diff / 1000);\n if (sec < 60) return `${sec}s ago`;\n const min = Math.round(sec / 60);\n if (min < 60) return `${min}m ago`;\n const hr = Math.round(min / 60);\n if (hr < 24) return `${hr}h ago`;\n const day = Math.round(hr / 24);\n return `${day}d ago`;\n}\n\nfunction formatDuration(ms?: number): string {\n if (ms === undefined || ms === null) return '—';\n if (ms < 1000) return `${ms}ms`;\n const sec = ms / 1000;\n if (sec < 60) return `${sec.toFixed(1)}s`;\n const min = sec / 60;\n if (min < 60) return `${min.toFixed(1)}m`;\n const hr = min / 60;\n return `${hr.toFixed(1)}h`;\n}\n\n/**\n * Prettify the agent's dotted stage id (e.g. `pull_request.fast` →\n * `pull_request fast`). We don't gate on the canonical 14-stage taxonomy\n * here because providers can — in principle — emit arbitrary stages and\n * we want to surface them rather than hide them.\n */\nfunction formatStage(stage: string): string {\n return stage.replace(/\\./g, ' ');\n}\n\nfunction SummaryCell({ run }: { run: SecurityScanRun }): ReactElement {\n const s = run.summary;\n const total = s.critical + s.high + s.medium + s.low + s.info;\n if (total === 0) {\n return <span className=\"text-xs text-text-tertiary\">clean</span>;\n }\n return (\n <div className=\"flex items-center gap-1 text-xs\">\n {s.critical > 0 && (\n <span\n className=\"inline-flex items-center gap-0.5 rounded px-1.5 py-0.5 bg-status-error-bg/20 text-status-error-text font-mono\"\n title={`${s.critical} critical`}\n >\n C {s.critical}\n </span>\n )}\n {s.high > 0 && (\n <span\n className=\"inline-flex items-center gap-0.5 rounded px-1.5 py-0.5 bg-status-warning-bg/20 text-status-warning-text font-mono\"\n title={`${s.high} high`}\n >\n H {s.high}\n </span>\n )}\n {s.medium > 0 && (\n <span\n className=\"inline-flex items-center gap-0.5 rounded px-1.5 py-0.5 bg-status-warning-bg/10 text-status-warning-text/80 font-mono\"\n title={`${s.medium} medium`}\n >\n M {s.medium}\n </span>\n )}\n {s.low > 0 && (\n <span\n className=\"inline-flex items-center gap-0.5 rounded px-1.5 py-0.5 bg-bg-sunken text-text-secondary font-mono\"\n title={`${s.low} low`}\n >\n L {s.low}\n </span>\n )}\n {s.info > 0 && (\n <span\n className=\"inline-flex items-center gap-0.5 rounded px-1.5 py-0.5 bg-bg-sunken text-text-tertiary font-mono\"\n title={`${s.info} info`}\n >\n I {s.info}\n </span>\n )}\n </div>\n );\n}\n\nfunction SkeletonRow({ rowKey }: { rowKey: number }): ReactElement {\n return (\n <tr key={rowKey} className=\"border-t border-border-subtle\">\n <td className=\"px-3 py-2\">\n <div className=\"h-3 w-20 rounded bg-bg-sunken animate-pulse\" />\n </td>\n <td className=\"px-3 py-2\">\n <div className=\"h-3 w-24 rounded bg-bg-sunken animate-pulse\" />\n </td>\n <td className=\"px-3 py-2\">\n <div className=\"h-3 w-16 rounded bg-bg-sunken animate-pulse\" />\n </td>\n <td className=\"px-3 py-2\">\n <div className=\"h-3 w-16 rounded bg-bg-sunken animate-pulse\" />\n </td>\n <td className=\"px-3 py-2\">\n <div className=\"h-3 w-20 rounded bg-bg-sunken animate-pulse\" />\n </td>\n <td className=\"px-3 py-2\">\n <div className=\"h-3 w-14 rounded bg-bg-sunken animate-pulse\" />\n </td>\n <td className=\"px-3 py-2\">\n <div className=\"h-3 w-28 rounded bg-bg-sunken animate-pulse\" />\n </td>\n </tr>\n );\n}\n\nexport function SecurityScanList({ vibeId, agentId, profile, limit = 20 }: Props): ReactElement {\n const ctx = useMemo(() => ({ agentId, profile }), [agentId, profile]);\n const { data, isLoading, error, isFetching, refetch } = useSecurityRecentScansQuery(\n ctx,\n vibeId,\n limit\n );\n\n // F4 Wave C: agentApiKey + tunnelUrl no longer needed in the browser —\n // the svc proxy owns them. A non-null `agentId` is the only readiness\n // gate (svc rejects unreachable agents with a structured envelope).\n const ready = !!agentId;\n\n if (!ready) {\n return (\n <section\n data-testid=\"security-scan-list\"\n className=\"rounded-lg border border-border-default bg-bg-surface p-4 text-sm text-text-secondary\"\n >\n Connect to a reachable agent to load scan history. Scan runs are read from the agent via the\n svc GraphQL proxy — there's no backend cache.\n </section>\n );\n }\n\n return (\n <section\n data-testid=\"security-scan-list\"\n className=\"rounded-lg border border-border-default bg-bg-surface\"\n >\n <header className=\"flex items-center justify-between gap-3 border-b border-border-default px-4 py-3\">\n <div className=\"min-w-0\">\n <h3 className=\"text-sm font-semibold text-text-primary\">Recent scan runs</h3>\n <p className=\"text-xs text-text-secondary mt-0.5\">\n Read live from the agent. Showing up to {limit} most recently active runs for this vibe.\n </p>\n </div>\n <button\n type=\"button\"\n onClick={() => void refetch()}\n disabled={isFetching}\n className=\"px-2.5 py-1 text-xs rounded-md border border-border-default bg-bg-sunken text-text-primary hover:bg-action-primary-bg-hover disabled:opacity-50\"\n >\n {isFetching ? 'Refreshing…' : 'Refresh'}\n </button>\n </header>\n\n {error && (\n <div className=\"m-4 flex items-start gap-2 rounded-md border border-status-error-border bg-status-error-bg/10 p-3 text-sm\">\n <AlertCircle className=\"size-4 text-status-error-text mt-0.5 shrink-0\" />\n <div className=\"text-text-primary\">\n <p className=\"font-medium\">Couldn't load scan runs</p>\n <p className=\"mt-0.5 text-xs text-text-secondary\">{error.message}</p>\n </div>\n </div>\n )}\n\n <div className=\"overflow-x-auto\">\n <table className=\"w-full text-sm\">\n <thead className=\"bg-bg-sunken/40 text-text-secondary\">\n <tr>\n <th className=\"px-3 py-2 text-left text-xs font-medium\">Stage</th>\n <th className=\"px-3 py-2 text-left text-xs font-medium\">Provider</th>\n <th className=\"px-3 py-2 text-left text-xs font-medium\">Status</th>\n <th className=\"px-3 py-2 text-left text-xs font-medium\">Conclusion</th>\n <th className=\"px-3 py-2 text-left text-xs font-medium\">Started</th>\n <th className=\"px-3 py-2 text-left text-xs font-medium\">Duration</th>\n <th className=\"px-3 py-2 text-left text-xs font-medium\">Findings</th>\n </tr>\n </thead>\n <tbody>\n {isLoading && data === undefined && (\n <>\n <SkeletonRow rowKey={0} />\n <SkeletonRow rowKey={1} />\n <SkeletonRow rowKey={2} />\n </>\n )}\n {!isLoading && (data?.length ?? 0) === 0 && !error && (\n <tr>\n <td colSpan={7} className=\"px-3 py-8\">\n <div className=\"flex flex-col items-center gap-2 text-center\">\n <ShieldAlert className=\"size-6 text-text-tertiary\" />\n <p className=\"text-sm text-text-primary\">No scans yet</p>\n <p className=\"text-xs text-text-secondary max-w-md\">\n Install a provider above and run a scan to see results here. Findings live in\n the agent's local SQLite cache.\n </p>\n </div>\n </td>\n </tr>\n )}\n {(data ?? []).map((run) => (\n <tr key={run.runId} className=\"border-t border-border-subtle align-middle\">\n <td className=\"px-3 py-2\">\n <span className=\"inline-flex items-center rounded-md bg-bg-sunken px-2 py-0.5 text-xs text-text-primary\">\n {formatStage(run.stage)}\n </span>\n </td>\n <td className=\"px-3 py-2 text-xs text-text-primary\">{run.providerName}</td>\n <td className=\"px-3 py-2\">\n <span\n className={`inline-flex items-center gap-1 rounded-md px-2 py-0.5 text-xs font-medium ${STATUS_BADGE[run.status]}`}\n >\n {statusIcon(run.status)}\n {run.status}\n </span>\n </td>\n <td className=\"px-3 py-2\">\n {run.conclusion ? (\n <span\n className={`inline-flex items-center rounded-md px-2 py-0.5 text-xs font-medium ${CONCLUSION_BADGE[run.conclusion]}`}\n >\n {run.conclusion}\n </span>\n ) : (\n <span className=\"text-xs text-text-tertiary\">—</span>\n )}\n </td>\n <td className=\"px-3 py-2 text-xs text-text-secondary\">\n <span title={run.startedAt ? new Date(run.startedAt).toLocaleString() : ''}>\n {relativeTime(run.startedAt)}\n </span>\n </td>\n <td className=\"px-3 py-2 text-xs text-text-secondary font-mono\">\n {formatDuration(run.durationMs)}\n </td>\n <td className=\"px-3 py-2\">\n <SummaryCell run={run} />\n </td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n </section>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"SecurityScanList.js","names":[],"sources":["../../../src/components/security/SecurityScanList.tsx"],"sourcesContent":["/**\n * Recent scan runs list for a vibe, read directly from the agent over\n * the tunnel. No backend GraphQL — see `./agentClient.ts` for the\n * REST surface and `./hooks.ts` for the React Query wrappers.\n *\n * The agent doesn't expose a list-runs endpoint; we derive distinct\n * run ids from the findings cache (see `listRecentScanRuns`). When a\n * vibe has zero findings the list will be empty even if scans have\n * been queued — that's correct: an in-flight queued/running scan\n * doesn't yet have any findings, and the empty state already nudges\n * the user toward kicking off a scan.\n */\nimport { useMemo, type ReactElement } from 'react';\nimport {\n AlertCircle,\n CheckCircle2,\n Clock,\n Loader2,\n MinusCircle,\n ShieldAlert,\n XCircle,\n} from 'lucide-react';\n\nimport { useSecurityRecentScansQuery, type UseSecurityAgentContext } from './hooks';\nimport type { AgentScanConclusion, AgentScanStatus, SecurityScanRun } from './agentClient';\n\ninterface Props extends UseSecurityAgentContext {\n vibeId: string;\n /** Max rows to surface. Defaults to 20. */\n limit?: number;\n}\n\nconst STATUS_BADGE: Record<AgentScanStatus, string> = {\n queued: 'bg-bg-sunken text-text-secondary',\n running: 'bg-status-info-bg-subtle text-status-info-text',\n succeeded: 'bg-status-success-bg-subtle text-status-success-text',\n failed: 'bg-status-error-bg-subtle text-status-error-text',\n errored: 'bg-status-error-bg-subtle text-status-error-text',\n cancelled: 'bg-bg-sunken text-text-tertiary',\n};\n\nconst CONCLUSION_BADGE: Record<AgentScanConclusion, string> = {\n pass: 'bg-status-success-bg-subtle text-status-success-text',\n warn: 'bg-status-warning-bg-subtle text-status-warning-text',\n fail: 'bg-status-error-bg-subtle text-status-error-text',\n skipped: 'bg-bg-sunken text-text-tertiary',\n};\n\nfunction statusIcon(status: AgentScanStatus): ReactElement {\n switch (status) {\n case 'queued':\n return <Clock className=\"size-3.5\" aria-hidden=\"true\" />;\n case 'running':\n return <Loader2 className=\"size-3.5 animate-spin\" aria-hidden=\"true\" />;\n case 'succeeded':\n return <CheckCircle2 className=\"size-3.5\" aria-hidden=\"true\" />;\n case 'failed':\n case 'errored':\n return <XCircle className=\"size-3.5\" aria-hidden=\"true\" />;\n case 'cancelled':\n return <MinusCircle className=\"size-3.5\" aria-hidden=\"true\" />;\n }\n}\n\nfunction relativeTime(epochMs?: number): string {\n if (!epochMs) return '—';\n const diff = Date.now() - epochMs;\n const sec = Math.round(diff / 1000);\n if (sec < 60) return `${sec}s ago`;\n const min = Math.round(sec / 60);\n if (min < 60) return `${min}m ago`;\n const hr = Math.round(min / 60);\n if (hr < 24) return `${hr}h ago`;\n const day = Math.round(hr / 24);\n return `${day}d ago`;\n}\n\nfunction formatDuration(ms?: number): string {\n if (ms === undefined || ms === null) return '—';\n if (ms < 1000) return `${ms}ms`;\n const sec = ms / 1000;\n if (sec < 60) return `${sec.toFixed(1)}s`;\n const min = sec / 60;\n if (min < 60) return `${min.toFixed(1)}m`;\n const hr = min / 60;\n return `${hr.toFixed(1)}h`;\n}\n\n/**\n * Prettify the agent's dotted stage id (e.g. `pull_request.fast` →\n * `pull_request fast`). We don't gate on the canonical 14-stage taxonomy\n * here because providers can — in principle — emit arbitrary stages and\n * we want to surface them rather than hide them.\n */\nfunction formatStage(stage: string): string {\n return stage.replace(/\\./g, ' ');\n}\n\nfunction SummaryCell({ run }: { run: SecurityScanRun }): ReactElement {\n const s = run.summary;\n const total = s.critical + s.high + s.medium + s.low + s.info;\n if (total === 0) {\n return <span className=\"text-xs text-text-tertiary\">clean</span>;\n }\n return (\n <div className=\"flex items-center gap-1 text-xs\">\n {s.critical > 0 && (\n <span\n className=\"inline-flex items-center gap-0.5 rounded px-1.5 py-0.5 bg-status-error-bg-subtle text-status-error-text font-mono\"\n title={`${s.critical} critical`}\n >\n C {s.critical}\n </span>\n )}\n {s.high > 0 && (\n <span\n className=\"inline-flex items-center gap-0.5 rounded px-1.5 py-0.5 bg-status-warning-bg-subtle text-status-warning-text font-mono\"\n title={`${s.high} high`}\n >\n H {s.high}\n </span>\n )}\n {s.medium > 0 && (\n <span\n className=\"inline-flex items-center gap-0.5 rounded px-1.5 py-0.5 bg-status-warning-bg-subtle text-status-warning-text/80 font-mono\"\n title={`${s.medium} medium`}\n >\n M {s.medium}\n </span>\n )}\n {s.low > 0 && (\n <span\n className=\"inline-flex items-center gap-0.5 rounded px-1.5 py-0.5 bg-bg-sunken text-text-secondary font-mono\"\n title={`${s.low} low`}\n >\n L {s.low}\n </span>\n )}\n {s.info > 0 && (\n <span\n className=\"inline-flex items-center gap-0.5 rounded px-1.5 py-0.5 bg-bg-sunken text-text-tertiary font-mono\"\n title={`${s.info} info`}\n >\n I {s.info}\n </span>\n )}\n </div>\n );\n}\n\nfunction SkeletonRow({ rowKey }: { rowKey: number }): ReactElement {\n return (\n <tr key={rowKey} className=\"border-t border-border-subtle\">\n <td className=\"px-3 py-2\">\n <div className=\"h-3 w-20 rounded bg-bg-sunken animate-pulse\" />\n </td>\n <td className=\"px-3 py-2\">\n <div className=\"h-3 w-24 rounded bg-bg-sunken animate-pulse\" />\n </td>\n <td className=\"px-3 py-2\">\n <div className=\"h-3 w-16 rounded bg-bg-sunken animate-pulse\" />\n </td>\n <td className=\"px-3 py-2\">\n <div className=\"h-3 w-16 rounded bg-bg-sunken animate-pulse\" />\n </td>\n <td className=\"px-3 py-2\">\n <div className=\"h-3 w-20 rounded bg-bg-sunken animate-pulse\" />\n </td>\n <td className=\"px-3 py-2\">\n <div className=\"h-3 w-14 rounded bg-bg-sunken animate-pulse\" />\n </td>\n <td className=\"px-3 py-2\">\n <div className=\"h-3 w-28 rounded bg-bg-sunken animate-pulse\" />\n </td>\n </tr>\n );\n}\n\nexport function SecurityScanList({ vibeId, agentId, profile, limit = 20 }: Props): ReactElement {\n const ctx = useMemo(() => ({ agentId, profile }), [agentId, profile]);\n const { data, isLoading, error, isFetching, refetch } = useSecurityRecentScansQuery(\n ctx,\n vibeId,\n limit\n );\n\n // F4 Wave C: agentApiKey + tunnelUrl no longer needed in the browser —\n // the svc proxy owns them. A non-null `agentId` is the only readiness\n // gate (svc rejects unreachable agents with a structured envelope).\n const ready = !!agentId;\n\n if (!ready) {\n return (\n <section\n data-testid=\"security-scan-list\"\n className=\"rounded-lg border border-border-default bg-bg-surface p-4 text-sm text-text-secondary\"\n >\n Connect to a reachable agent to load scan history. Scan runs are read from the agent via the\n svc GraphQL proxy — there's no backend cache.\n </section>\n );\n }\n\n return (\n <section\n data-testid=\"security-scan-list\"\n className=\"rounded-lg border border-border-default bg-bg-surface\"\n >\n <header className=\"flex items-center justify-between gap-3 border-b border-border-default px-4 py-3\">\n <div className=\"min-w-0\">\n <h3 className=\"text-sm font-semibold text-text-primary\">Recent scan runs</h3>\n <p className=\"text-xs text-text-secondary mt-0.5\">\n Read live from the agent. Showing up to {limit} most recently active runs for this vibe.\n </p>\n </div>\n <button\n type=\"button\"\n onClick={() => void refetch()}\n disabled={isFetching}\n className=\"px-2.5 py-1 text-xs rounded-md border border-border-default bg-bg-sunken text-text-primary hover:bg-action-primary-bg-hover disabled:opacity-50\"\n >\n {isFetching ? 'Refreshing…' : 'Refresh'}\n </button>\n </header>\n\n {error && (\n <div className=\"m-4 flex items-start gap-2 rounded-md border border-status-error-border bg-status-error-bg-subtle/10 p-3 text-sm\">\n <AlertCircle className=\"size-4 text-status-error-text mt-0.5 shrink-0\" />\n <div className=\"text-text-primary\">\n <p className=\"font-medium\">Couldn't load scan runs</p>\n <p className=\"mt-0.5 text-xs text-text-secondary\">{error.message}</p>\n </div>\n </div>\n )}\n\n <div className=\"overflow-x-auto\">\n <table className=\"w-full text-sm\">\n <thead className=\"bg-bg-sunken/40 text-text-secondary\">\n <tr>\n <th className=\"px-3 py-2 text-left text-xs font-medium\">Stage</th>\n <th className=\"px-3 py-2 text-left text-xs font-medium\">Provider</th>\n <th className=\"px-3 py-2 text-left text-xs font-medium\">Status</th>\n <th className=\"px-3 py-2 text-left text-xs font-medium\">Conclusion</th>\n <th className=\"px-3 py-2 text-left text-xs font-medium\">Started</th>\n <th className=\"px-3 py-2 text-left text-xs font-medium\">Duration</th>\n <th className=\"px-3 py-2 text-left text-xs font-medium\">Findings</th>\n </tr>\n </thead>\n <tbody>\n {isLoading && data === undefined && (\n <>\n <SkeletonRow rowKey={0} />\n <SkeletonRow rowKey={1} />\n <SkeletonRow rowKey={2} />\n </>\n )}\n {!isLoading && (data?.length ?? 0) === 0 && !error && (\n <tr>\n <td colSpan={7} className=\"px-3 py-8\">\n <div className=\"flex flex-col items-center gap-2 text-center\">\n <ShieldAlert className=\"size-6 text-text-tertiary\" />\n <p className=\"text-sm text-text-primary\">No scans yet</p>\n <p className=\"text-xs text-text-secondary max-w-md\">\n Install a provider above and run a scan to see results here. Findings live in\n the agent's local SQLite cache.\n </p>\n </div>\n </td>\n </tr>\n )}\n {(data ?? []).map((run) => (\n <tr key={run.runId} className=\"border-t border-border-subtle align-middle\">\n <td className=\"px-3 py-2\">\n <span className=\"inline-flex items-center rounded-md bg-bg-sunken px-2 py-0.5 text-xs text-text-primary\">\n {formatStage(run.stage)}\n </span>\n </td>\n <td className=\"px-3 py-2 text-xs text-text-primary\">{run.providerName}</td>\n <td className=\"px-3 py-2\">\n <span\n className={`inline-flex items-center gap-1 rounded-md px-2 py-0.5 text-xs font-medium ${STATUS_BADGE[run.status]}`}\n >\n {statusIcon(run.status)}\n {run.status}\n </span>\n </td>\n <td className=\"px-3 py-2\">\n {run.conclusion ? (\n <span\n className={`inline-flex items-center rounded-md px-2 py-0.5 text-xs font-medium ${CONCLUSION_BADGE[run.conclusion]}`}\n >\n {run.conclusion}\n </span>\n ) : (\n <span className=\"text-xs text-text-tertiary\">—</span>\n )}\n </td>\n <td className=\"px-3 py-2 text-xs text-text-secondary\">\n <span title={run.startedAt ? new Date(run.startedAt).toLocaleString() : ''}>\n {relativeTime(run.startedAt)}\n </span>\n </td>\n <td className=\"px-3 py-2 text-xs text-text-secondary font-mono\">\n {formatDuration(run.durationMs)}\n </td>\n <td className=\"px-3 py-2\">\n <SummaryCell run={run} />\n </td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n </section>\n );\n}\n"],"mappings":";;;;;AAgCA,IAAM,IAAgD;CACpD,QAAQ;CACR,SAAS;CACT,WAAW;CACX,QAAQ;CACR,SAAS;CACT,WAAW;CACZ,EAEK,IAAwD;CAC5D,MAAM;CACN,MAAM;CACN,MAAM;CACN,SAAS;CACV;AAED,SAAS,EAAW,GAAuC;AACzD,SAAQ,GAAR;EACE,KAAK,SACH,QAAO,kBAAC,GAAD;GAAO,WAAU;GAAW,eAAY;GAAS,CAAA;EAC1D,KAAK,UACH,QAAO,kBAAC,GAAD;GAAS,WAAU;GAAwB,eAAY;GAAS,CAAA;EACzE,KAAK,YACH,QAAO,kBAAC,GAAD;GAAc,WAAU;GAAW,eAAY;GAAS,CAAA;EACjE,KAAK;EACL,KAAK,UACH,QAAO,kBAAC,GAAD;GAAS,WAAU;GAAW,eAAY;GAAS,CAAA;EAC5D,KAAK,YACH,QAAO,kBAAC,GAAD;GAAa,WAAU;GAAW,eAAY;GAAS,CAAA;;;AAIpE,SAAS,EAAa,GAA0B;AAC9C,KAAI,CAAC,EAAS,QAAO;CACrB,IAAM,IAAO,KAAK,KAAK,GAAG,GACpB,IAAM,KAAK,MAAM,IAAO,IAAK;AACnC,KAAI,IAAM,GAAI,QAAO,GAAG,EAAI;CAC5B,IAAM,IAAM,KAAK,MAAM,IAAM,GAAG;AAChC,KAAI,IAAM,GAAI,QAAO,GAAG,EAAI;CAC5B,IAAM,IAAK,KAAK,MAAM,IAAM,GAAG;AAG/B,QAFI,IAAK,KAAW,GAAG,EAAG,SAEnB,GADK,KAAK,MAAM,IAAK,GAAG,CACjB;;AAGhB,SAAS,EAAe,GAAqB;AAC3C,KAAI,KAA2B,KAAM,QAAO;AAC5C,KAAI,IAAK,IAAM,QAAO,GAAG,EAAG;CAC5B,IAAM,IAAM,IAAK;AACjB,KAAI,IAAM,GAAI,QAAO,GAAG,EAAI,QAAQ,EAAE,CAAC;CACvC,IAAM,IAAM,IAAM;AAGlB,QAFI,IAAM,KAAW,GAAG,EAAI,QAAQ,EAAE,CAAC,KAEhC,IADI,IAAM,IACJ,QAAQ,EAAE,CAAC;;AAS1B,SAAS,EAAY,GAAuB;AAC1C,QAAO,EAAM,QAAQ,OAAO,IAAI;;AAGlC,SAAS,EAAY,EAAE,UAA+C;CACpE,IAAM,IAAI,EAAI;AAKd,QAJc,EAAE,WAAW,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,SAC3C,IACL,kBAAC,QAAD;EAAM,WAAU;YAA6B;EAAY,CAAA,GAGhE,kBAAC,OAAD;EAAK,WAAU;YAAf;GACG,EAAE,WAAW,KACZ,kBAAC,QAAD;IACE,WAAU;IACV,OAAO,GAAG,EAAE,SAAS;cAFvB,CAGC,MACI,EAAE,SACA;;GAER,EAAE,OAAO,KACR,kBAAC,QAAD;IACE,WAAU;IACV,OAAO,GAAG,EAAE,KAAK;cAFnB,CAGC,MACI,EAAE,KACA;;GAER,EAAE,SAAS,KACV,kBAAC,QAAD;IACE,WAAU;IACV,OAAO,GAAG,EAAE,OAAO;cAFrB,CAGC,MACI,EAAE,OACA;;GAER,EAAE,MAAM,KACP,kBAAC,QAAD;IACE,WAAU;IACV,OAAO,GAAG,EAAE,IAAI;cAFlB,CAGC,MACI,EAAE,IACA;;GAER,EAAE,OAAO,KACR,kBAAC,QAAD;IACE,WAAU;IACV,OAAO,GAAG,EAAE,KAAK;cAFnB,CAGC,MACI,EAAE,KACA;;GAEL;;;AAIV,SAAS,EAAY,EAAE,aAA4C;AACjE,QACE,kBAAC,MAAD;EAAiB,WAAU;YAA3B;GACE,kBAAC,MAAD;IAAI,WAAU;cACZ,kBAAC,OAAD,EAAK,WAAU,+CAAgD,CAAA;IAC5D,CAAA;GACL,kBAAC,MAAD;IAAI,WAAU;cACZ,kBAAC,OAAD,EAAK,WAAU,+CAAgD,CAAA;IAC5D,CAAA;GACL,kBAAC,MAAD;IAAI,WAAU;cACZ,kBAAC,OAAD,EAAK,WAAU,+CAAgD,CAAA;IAC5D,CAAA;GACL,kBAAC,MAAD;IAAI,WAAU;cACZ,kBAAC,OAAD,EAAK,WAAU,+CAAgD,CAAA;IAC5D,CAAA;GACL,kBAAC,MAAD;IAAI,WAAU;cACZ,kBAAC,OAAD,EAAK,WAAU,+CAAgD,CAAA;IAC5D,CAAA;GACL,kBAAC,MAAD;IAAI,WAAU;cACZ,kBAAC,OAAD,EAAK,WAAU,+CAAgD,CAAA;IAC5D,CAAA;GACL,kBAAC,MAAD;IAAI,WAAU;cACZ,kBAAC,OAAD,EAAK,WAAU,+CAAgD,CAAA;IAC5D,CAAA;GACF;IAtBI,EAsBJ;;AAIT,SAAgB,EAAiB,EAAE,WAAQ,YAAS,YAAS,WAAQ,MAA2B;CAE9F,IAAM,EAAE,SAAM,cAAW,UAAO,eAAY,eAAY,EAD5C,SAAe;EAAE;EAAS;EAAS,GAAG,CAAC,GAAS,EAAQ,CAAC,EAGnE,GACA,EACD;AAmBD,QAdgB,IAed,kBAAC,WAAD;EACE,eAAY;EACZ,WAAU;YAFZ;GAIE,kBAAC,UAAD;IAAQ,WAAU;cAAlB,CACE,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,MAAD;MAAI,WAAU;gBAA0C;MAAqB,CAAA,EAC7E,kBAAC,KAAD;MAAG,WAAU;gBAAb;OAAkD;OACP;OAAM;OAC7C;QACA;QACN,kBAAC,UAAD;KACE,MAAK;KACL,eAAe,KAAK,GAAS;KAC7B,UAAU;KACV,WAAU;eAET,IAAa,gBAAgB;KACvB,CAAA,CACF;;GAER,KACC,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,GAAD,EAAa,WAAU,iDAAkD,CAAA,EACzE,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,KAAD;MAAG,WAAU;gBAAc;MAA2B,CAAA,EACtD,kBAAC,KAAD;MAAG,WAAU;gBAAsC,EAAM;MAAY,CAAA,CACjE;OACF;;GAGR,kBAAC,OAAD;IAAK,WAAU;cACb,kBAAC,SAAD;KAAO,WAAU;eAAjB,CACE,kBAAC,SAAD;MAAO,WAAU;gBACf,kBAAC,MAAD,EAAA,UAAA;OACE,kBAAC,MAAD;QAAI,WAAU;kBAA0C;QAAU,CAAA;OAClE,kBAAC,MAAD;QAAI,WAAU;kBAA0C;QAAa,CAAA;OACrE,kBAAC,MAAD;QAAI,WAAU;kBAA0C;QAAW,CAAA;OACnE,kBAAC,MAAD;QAAI,WAAU;kBAA0C;QAAe,CAAA;OACvE,kBAAC,MAAD;QAAI,WAAU;kBAA0C;QAAY,CAAA;OACpE,kBAAC,MAAD;QAAI,WAAU;kBAA0C;QAAa,CAAA;OACrE,kBAAC,MAAD;QAAI,WAAU;kBAA0C;QAAa,CAAA;OAClE,EAAA,CAAA;MACC,CAAA,EACR,kBAAC,SAAD,EAAA,UAAA;MACG,KAAa,MAAS,KAAA,KACrB,kBAAA,GAAA,EAAA,UAAA;OACE,kBAAC,GAAD,EAAa,QAAQ,GAAK,CAAA;OAC1B,kBAAC,GAAD,EAAa,QAAQ,GAAK,CAAA;OAC1B,kBAAC,GAAD,EAAa,QAAQ,GAAK,CAAA;OACzB,EAAA,CAAA;MAEJ,CAAC,MAAc,GAAM,UAAU,OAAO,KAAK,CAAC,KAC3C,kBAAC,MAAD,EAAA,UACE,kBAAC,MAAD;OAAI,SAAS;OAAG,WAAU;iBACxB,kBAAC,OAAD;QAAK,WAAU;kBAAf;SACE,kBAAC,GAAD,EAAa,WAAU,6BAA8B,CAAA;SACrD,kBAAC,KAAD;UAAG,WAAU;oBAA4B;UAAgB,CAAA;SACzD,kBAAC,KAAD;UAAG,WAAU;oBAAuC;UAGhD,CAAA;SACA;;OACH,CAAA,EACF,CAAA;OAEL,KAAQ,EAAE,EAAE,KAAK,MACjB,kBAAC,MAAD;OAAoB,WAAU;iBAA9B;QACE,kBAAC,MAAD;SAAI,WAAU;mBACZ,kBAAC,QAAD;UAAM,WAAU;oBACb,EAAY,EAAI,MAAM;UAClB,CAAA;SACJ,CAAA;QACL,kBAAC,MAAD;SAAI,WAAU;mBAAuC,EAAI;SAAkB,CAAA;QAC3E,kBAAC,MAAD;SAAI,WAAU;mBACZ,kBAAC,QAAD;UACE,WAAW,6EAA6E,EAAa,EAAI;oBAD3G,CAGG,EAAW,EAAI,OAAO,EACtB,EAAI,OACA;;SACJ,CAAA;QACL,kBAAC,MAAD;SAAI,WAAU;mBACX,EAAI,aACH,kBAAC,QAAD;UACE,WAAW,uEAAuE,EAAiB,EAAI;oBAEtG,EAAI;UACA,CAAA,GAEP,kBAAC,QAAD;UAAM,WAAU;oBAA6B;UAAQ,CAAA;SAEpD,CAAA;QACL,kBAAC,MAAD;SAAI,WAAU;mBACZ,kBAAC,QAAD;UAAM,OAAO,EAAI,YAAY,IAAI,KAAK,EAAI,UAAU,CAAC,gBAAgB,GAAG;oBACrE,EAAa,EAAI,UAAU;UACvB,CAAA;SACJ,CAAA;QACL,kBAAC,MAAD;SAAI,WAAU;mBACX,EAAe,EAAI,WAAW;SAC5B,CAAA;QACL,kBAAC,MAAD;SAAI,WAAU;mBACZ,kBAAC,GAAD,EAAkB,QAAO,CAAA;SACtB,CAAA;QACF;SArCI,EAAI,MAqCR,CACL;MACI,EAAA,CAAA,CACF;;IACJ,CAAA;GACE;MAxHR,kBAAC,WAAD;EACE,eAAY;EACZ,WAAU;YACX;EAGS,CAAA"}
@@ -32,7 +32,7 @@ var _ = {
32
32
  }
33
33
  },
34
34
  whileTap: { scale: .98 },
35
- className: `bg-bg-surface border rounded-lg p-4 hover:border-border-strong hover:shadow-md transition-all cursor-pointer ${E ? "border-action-primary-bg ring-1 ring-action-primary-bg/30" : "border-border-default"} ${O}`,
35
+ className: `bg-bg-surface border rounded-card shadow-elevation-1 p-5 hover:border-border-strong hover:shadow-elevation-2 transition-all duration-200 ease-[var(--motion-easing-out)] cursor-pointer ${E ? "border-transparent ring-1 ring-[var(--color-focus-ring)] bg-[var(--color-accent-soft)]" : "border-border-subtle"} ${O}`,
36
36
  onClick: () => y?.(v),
37
37
  children: [
38
38
  /* @__PURE__ */ g("div", {
@@ -54,8 +54,8 @@ var _ = {
54
54
  })
55
55
  }),
56
56
  /* @__PURE__ */ h("div", {
57
- className: "flex-shrink-0 size-8 sm:w-10 sm:h-10 rounded-lg bg-bg-sunken flex items-center justify-center",
58
- children: /* @__PURE__ */ h(d, { className: "size-4 sm:w-5 sm:h-5 text-text-secondary" })
57
+ className: "flex-shrink-0 size-9 sm:size-10 rounded-lg bg-[var(--color-accent-soft)] flex items-center justify-center",
58
+ children: /* @__PURE__ */ h(d, { className: "size-4 sm:size-5 text-action-primary-bg" })
59
59
  }),
60
60
  /* @__PURE__ */ g("div", {
61
61
  className: "min-w-0 flex-1",
@@ -63,7 +63,7 @@ var _ = {
63
63
  /* @__PURE__ */ g("div", {
64
64
  className: "flex items-center gap-1.5 sm:gap-2 flex-wrap",
65
65
  children: [/* @__PURE__ */ h("h3", {
66
- className: "font-semibold text-text-primary truncate max-w-[120px] sm:max-w-none text-sm sm:text-base",
66
+ className: "font-semibold text-text-primary truncate max-w-[120px] sm:max-w-none text-sm sm:text-base tracking-tight",
67
67
  children: v.name
68
68
  }), /* @__PURE__ */ h("span", {
69
69
  className: "text-base sm:text-lg flex-shrink-0",
@@ -90,8 +90,12 @@ var _ = {
90
90
  onClick: (e) => {
91
91
  e.stopPropagation(), j(!A);
92
92
  },
93
- className: "p-1 sm:p-1.5 rounded hover:bg-bg-sunken transition-colors",
94
- children: /* @__PURE__ */ h(o, { className: "size-4 text-text-secondary" })
93
+ className: "inline-flex size-8 items-center justify-center rounded-button text-text-secondary hover:bg-bg-sunken hover:text-text-primary transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus",
94
+ "aria-label": k("vibecontrols.sessions.card.actions", "Session actions"),
95
+ children: /* @__PURE__ */ h(o, {
96
+ className: "size-4",
97
+ "aria-hidden": "true"
98
+ })
95
99
  }), /* @__PURE__ */ h(p, { children: A && /* @__PURE__ */ g(m.div, {
96
100
  initial: {
97
101
  opacity: 0,
@@ -109,14 +113,14 @@ var _ = {
109
113
  y: -4
110
114
  },
111
115
  transition: { duration: .15 },
112
- className: "absolute right-0 mt-1 w-40 bg-bg-surface border border-border-default rounded-lg shadow-lg z-10",
116
+ className: "absolute right-0 mt-1.5 w-44 bg-bg-elevated border border-border-subtle rounded-card shadow-elevation-3 z-10 overflow-hidden py-1",
113
117
  children: [
114
118
  P && b && /* @__PURE__ */ g(m.button, {
115
119
  whileTap: { scale: .97 },
116
120
  onClick: (e) => {
117
121
  e.stopPropagation(), b(v), j(!1);
118
122
  },
119
- className: "flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken rounded-t-lg",
123
+ className: "flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken transition-colors",
120
124
  children: [/* @__PURE__ */ h(s, { className: "size-4" }), k("vibecontrols.sessions.card.start", "Start")]
121
125
  }),
122
126
  N && x && /* @__PURE__ */ g(m.button, {
@@ -124,7 +128,7 @@ var _ = {
124
128
  onClick: (e) => {
125
129
  e.stopPropagation(), x(v), j(!1);
126
130
  },
127
- className: "flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken",
131
+ className: "flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken transition-colors",
128
132
  children: [/* @__PURE__ */ h(u, { className: "size-4" }), k("vibecontrols.sessions.card.stop", "Stop")]
129
133
  }),
130
134
  N && S && /* @__PURE__ */ g(m.button, {
@@ -132,7 +136,7 @@ var _ = {
132
136
  onClick: (e) => {
133
137
  e.stopPropagation(), S(v), j(!1);
134
138
  },
135
- className: "flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken",
139
+ className: "flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken transition-colors",
136
140
  children: [/* @__PURE__ */ h(c, { className: "size-4" }), k("vibecontrols.sessions.card.restart", "Restart")]
137
141
  }),
138
142
  N && w && /* @__PURE__ */ g(m.button, {
@@ -140,7 +144,7 @@ var _ = {
140
144
  onClick: (e) => {
141
145
  e.stopPropagation(), w(v), j(!1);
142
146
  },
143
- className: "flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken",
147
+ className: "flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken transition-colors",
144
148
  children: [/* @__PURE__ */ h(d, { className: "size-4" }), k("vibecontrols.sessions.card.openTerminal", "Open Terminal")]
145
149
  }),
146
150
  N && T && /* @__PURE__ */ g(m.button, {
@@ -148,7 +152,7 @@ var _ = {
148
152
  onClick: (e) => {
149
153
  e.stopPropagation(), T(v), j(!1);
150
154
  },
151
- className: "flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken",
155
+ className: "flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken transition-colors",
152
156
  children: [/* @__PURE__ */ h(l, { className: "size-4" }), k("vibecontrols.sessions.card.shareSession", "Share Session")]
153
157
  }),
154
158
  C && /* @__PURE__ */ g(m.button, {
@@ -156,7 +160,7 @@ var _ = {
156
160
  onClick: (e) => {
157
161
  e.stopPropagation(), C(v), j(!1);
158
162
  },
159
- className: "flex items-center gap-2 w-full px-3 py-2 text-sm text-status-error-text hover:bg-bg-sunken rounded-b-lg",
163
+ className: "flex items-center gap-2 w-full px-3 py-2 text-sm text-status-error-text hover:bg-status-error-bg-subtle transition-colors",
160
164
  children: [/* @__PURE__ */ h(f, { className: "size-4" }), k("vibecontrols.sessions.card.terminate", "Terminate")]
161
165
  })
162
166
  ]
@@ -167,7 +171,7 @@ var _ = {
167
171
  (() => {
168
172
  let e = v.metadata || {}, t = typeof e.tmuxSessionName == "string" && e.tmuxSessionName || typeof e.weztermWorkspace == "string" && e.weztermWorkspace || typeof e.zellijSessionName == "string" && e.zellijSessionName || typeof e.externalSessionName == "string" && e.externalSessionName || v.providerSessionName || null;
169
173
  return t ? /* @__PURE__ */ g("div", {
170
- className: "mt-2 text-[10px] font-mono text-text-tertiary",
174
+ className: "mt-3 text-[10px] font-mono text-text-muted",
171
175
  title: `Provider session: ${t}`,
172
176
  "data-testid": "session-card-external-name",
173
177
  children: [/* @__PURE__ */ g("span", {
@@ -180,20 +184,23 @@ var _ = {
180
184
  }) : null;
181
185
  })(),
182
186
  v.command && /* @__PURE__ */ g("div", {
183
- className: "mt-3 text-xs font-mono bg-bg-sunken p-2 rounded truncate text-text-secondary",
187
+ className: "mt-4 text-xs font-mono bg-bg-sunken/60 px-2 py-1.5 rounded truncate text-text-secondary",
184
188
  children: ["$ ", v.command]
185
189
  }),
186
190
  v.lastOutput && /* @__PURE__ */ h("div", {
187
- className: "mt-2 text-xs font-mono bg-bg-sunken text-status-success-text p-2 rounded truncate",
191
+ className: "mt-2 text-xs font-mono bg-bg-sunken/60 text-status-success-text px-2 py-1.5 rounded truncate",
188
192
  children: v.lastOutput
189
193
  }),
190
194
  /* @__PURE__ */ g("div", {
191
- className: "mt-3 flex items-center justify-between text-sm text-text-secondary",
195
+ className: "mt-4 flex items-center justify-between text-sm text-text-secondary",
192
196
  children: [/* @__PURE__ */ g("div", {
193
197
  className: "flex items-center gap-2",
194
- children: [/* @__PURE__ */ h(a, { className: "size-4" }), /* @__PURE__ */ h("span", { children: M(v.updatedAt) })]
198
+ children: [/* @__PURE__ */ h(a, { className: "size-4" }), /* @__PURE__ */ h("span", {
199
+ className: "tabular-nums",
200
+ children: M(v.updatedAt)
201
+ })]
195
202
  }), v.pid && /* @__PURE__ */ g("span", {
196
- className: "text-xs font-mono",
203
+ className: "text-xs font-mono tabular-nums",
197
204
  children: ["PID: ", v.pid]
198
205
  })]
199
206
  })
@@ -1 +1 @@
1
- {"version":3,"file":"SessionCard.js","names":[],"sources":["../../../src/components/sessions/SessionCard.tsx"],"sourcesContent":["import type { FC } from 'react';\nimport { useState } from 'react';\nimport { motion, AnimatePresence } from 'framer-motion';\nimport {\n Terminal,\n Clock,\n MoreVertical,\n Play,\n Square,\n RotateCcw,\n Trash2,\n Share2,\n} from 'lucide-react';\nimport { SessionStatusBadge } from './SessionStatusBadge';\nimport { AgentInfoPopover, TargetInfoPopover } from './EntityInfoPopover';\nimport type { Session } from './types';\nimport { useTr } from '../../shared/hooks/useTr';\n\nexport interface SessionCardProps {\n session: Session;\n onOpen?: (session: Session) => void;\n onStart?: (session: Session) => void;\n onStop?: (session: Session) => void;\n onRestart?: (session: Session) => void;\n onTerminate?: (session: Session) => void;\n onOpenTerminal?: (session: Session) => void;\n /** Callback to open the share dialog */\n onShare?: (session: Session) => void;\n /** Whether this card is selected for bulk operations */\n selected?: boolean;\n /** Toggle selection for bulk operations */\n onToggleSelect?: () => void;\n className?: string;\n}\n\nconst sessionTypeIcons: Record<string, string> = {\n TMUX: '🖥️',\n SSH: '🔐',\n TERMINAL: '💻',\n SCRIPT: '📜',\n CUSTOM: '⚙️',\n};\n\n/**\n * SessionCard - Display terminal session information\n */\nexport const SessionCard: FC<SessionCardProps> = ({\n session,\n onOpen,\n onStart,\n onStop,\n onRestart,\n onTerminate,\n onOpenTerminal,\n onShare,\n selected = false,\n onToggleSelect,\n className = '',\n}) => {\n const tr = useTr();\n const [showMenu, setShowMenu] = useState(false);\n\n const formatTime = (timestamp: string | undefined | null) => {\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 new Date(timestamp).toLocaleDateString();\n };\n\n const isRunning = session.status === 'RUNNING';\n const isStopped = session.status === 'STOPPED' || session.status === 'TERMINATED';\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 hover:border-border-strong hover:shadow-md transition-all cursor-pointer ${selected ? 'border-action-primary-bg ring-1 ring-action-primary-bg/30' : 'border-border-default'} ${className}`}\n onClick={() => onOpen?.(session)}\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 {onToggleSelect && (\n <div className=\"flex-shrink-0 pt-0.5\">\n <input\n aria-label=\"control\"\n type=\"checkbox\"\n checked={selected}\n onChange={(e) => {\n e.stopPropagation();\n onToggleSelect();\n }}\n onClick={(e) => e.stopPropagation()}\n className=\"size-4 rounded border-border-default cursor-pointer\"\n />\n </div>\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 <Terminal 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 flex-wrap\">\n <h3 className=\"font-semibold text-text-primary truncate max-w-[120px] sm:max-w-none text-sm sm:text-base\">\n {session.name}\n </h3>\n <span className=\"text-base sm:text-lg flex-shrink-0\">\n {sessionTypeIcons[session.type] || '💻'}\n </span>\n </div>\n <p className=\"text-xs sm:text-sm text-text-secondary\">{session.type}</p>\n {session.agent && (\n <div className=\"mt-1.5 flex items-center gap-1 flex-wrap\">\n <AgentInfoPopover agent={session.agent} />\n {session.agent.target && <TargetInfoPopover target={session.agent.target} />}\n </div>\n )}\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 <SessionStatusBadge status={session.status} />\n {(onStart || onStop || onRestart || onTerminate) && (\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\"\n >\n <MoreVertical className=\"size-4 text-text-secondary\" />\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 {isStopped && onStart && (\n <motion.button\n whileTap={{ scale: 0.97 }}\n onClick={(e) => {\n e.stopPropagation();\n onStart(session);\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('vibecontrols.sessions.card.start', 'Start')}\n </motion.button>\n )}\n {isRunning && onStop && (\n <motion.button\n whileTap={{ scale: 0.97 }}\n onClick={(e) => {\n e.stopPropagation();\n onStop(session);\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 <Square className=\"size-4\" />\n {tr('vibecontrols.sessions.card.stop', 'Stop')}\n </motion.button>\n )}\n {isRunning && onRestart && (\n <motion.button\n whileTap={{ scale: 0.97 }}\n onClick={(e) => {\n e.stopPropagation();\n onRestart(session);\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 <RotateCcw className=\"size-4\" />\n {tr('vibecontrols.sessions.card.restart', 'Restart')}\n </motion.button>\n )}\n {isRunning && onOpenTerminal && (\n <motion.button\n whileTap={{ scale: 0.97 }}\n onClick={(e) => {\n e.stopPropagation();\n onOpenTerminal(session);\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 <Terminal className=\"size-4\" />\n {tr('vibecontrols.sessions.card.openTerminal', 'Open Terminal')}\n </motion.button>\n )}\n {isRunning && onShare && (\n <motion.button\n whileTap={{ scale: 0.97 }}\n onClick={(e) => {\n e.stopPropagation();\n onShare(session);\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 <Share2 className=\"size-4\" />\n {tr('vibecontrols.sessions.card.shareSession', 'Share Session')}\n </motion.button>\n )}\n {onTerminate && (\n <motion.button\n whileTap={{ scale: 0.97 }}\n onClick={(e) => {\n e.stopPropagation();\n onTerminate(session);\n setShowMenu(false);\n }}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-sm text-status-error-text hover:bg-bg-sunken rounded-b-lg\"\n >\n <Trash2 className=\"size-4\" />\n {tr('vibecontrols.sessions.card.terminate', 'Terminate')}\n </motion.button>\n )}\n </motion.div>\n )}\n </AnimatePresence>\n </div>\n )}\n </div>\n </div>\n\n {/* Provider-native session name (e.g. tmux session name like\n `vibe-051c9c62`). Surfaced so users can recognise their session\n if they ever need to attach from a CLI (`tmux attach -t …`)\n and so reconnect-after-restart flows are obvious. */}\n {(() => {\n const meta = (session.metadata as Record<string, unknown> | null) || {};\n const externalName =\n (typeof meta.tmuxSessionName === 'string' && meta.tmuxSessionName) ||\n (typeof meta.weztermWorkspace === 'string' && meta.weztermWorkspace) ||\n (typeof meta.zellijSessionName === 'string' && meta.zellijSessionName) ||\n (typeof meta.externalSessionName === 'string' && meta.externalSessionName) ||\n session.providerSessionName ||\n null;\n return externalName ? (\n <div\n className=\"mt-2 text-[10px] font-mono text-text-tertiary\"\n title={`Provider session: ${externalName}`}\n data-testid=\"session-card-external-name\"\n >\n <span className=\"opacity-70\">{session.type.toLowerCase()}: </span>\n <span className=\"break-all\">{externalName}</span>\n </div>\n ) : null;\n })()}\n\n {session.command && (\n <div className=\"mt-3 text-xs font-mono bg-bg-sunken p-2 rounded truncate text-text-secondary\">\n $ {session.command}\n </div>\n )}\n\n {session.lastOutput && (\n <div className=\"mt-2 text-xs font-mono bg-bg-sunken text-status-success-text p-2 rounded truncate\">\n {session.lastOutput}\n </div>\n )}\n\n <div className=\"mt-3 flex items-center justify-between text-sm text-text-secondary\">\n <div className=\"flex items-center gap-2\">\n <Clock className=\"size-4\" />\n <span>{formatTime(session.updatedAt)}</span>\n </div>\n {session.pid && <span className=\"text-xs font-mono\">PID: {session.pid}</span>}\n </div>\n </motion.div>\n );\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"SessionCard.js","names":[],"sources":["../../../src/components/sessions/SessionCard.tsx"],"sourcesContent":["import type { FC } from 'react';\nimport { useState } from 'react';\nimport { motion, AnimatePresence } from 'framer-motion';\nimport {\n Terminal,\n Clock,\n MoreVertical,\n Play,\n Square,\n RotateCcw,\n Trash2,\n Share2,\n} from 'lucide-react';\nimport { SessionStatusBadge } from './SessionStatusBadge';\nimport { AgentInfoPopover, TargetInfoPopover } from './EntityInfoPopover';\nimport type { Session } from './types';\nimport { useTr } from '../../shared/hooks/useTr';\n\nexport interface SessionCardProps {\n session: Session;\n onOpen?: (session: Session) => void;\n onStart?: (session: Session) => void;\n onStop?: (session: Session) => void;\n onRestart?: (session: Session) => void;\n onTerminate?: (session: Session) => void;\n onOpenTerminal?: (session: Session) => void;\n /** Callback to open the share dialog */\n onShare?: (session: Session) => void;\n /** Whether this card is selected for bulk operations */\n selected?: boolean;\n /** Toggle selection for bulk operations */\n onToggleSelect?: () => void;\n className?: string;\n}\n\nconst sessionTypeIcons: Record<string, string> = {\n TMUX: '🖥️',\n SSH: '🔐',\n TERMINAL: '💻',\n SCRIPT: '📜',\n CUSTOM: '⚙️',\n};\n\n/**\n * SessionCard - Display terminal session information\n */\nexport const SessionCard: FC<SessionCardProps> = ({\n session,\n onOpen,\n onStart,\n onStop,\n onRestart,\n onTerminate,\n onOpenTerminal,\n onShare,\n selected = false,\n onToggleSelect,\n className = '',\n}) => {\n const tr = useTr();\n const [showMenu, setShowMenu] = useState(false);\n\n const formatTime = (timestamp: string | undefined | null) => {\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 new Date(timestamp).toLocaleDateString();\n };\n\n const isRunning = session.status === 'RUNNING';\n const isStopped = session.status === 'STOPPED' || session.status === 'TERMINATED';\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-card shadow-elevation-1 p-5 hover:border-border-strong hover:shadow-elevation-2 transition-all duration-200 ease-[var(--motion-easing-out)] cursor-pointer ${selected ? 'border-transparent ring-1 ring-[var(--color-focus-ring)] bg-[var(--color-accent-soft)]' : 'border-border-subtle'} ${className}`}\n onClick={() => onOpen?.(session)}\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 {onToggleSelect && (\n <div className=\"flex-shrink-0 pt-0.5\">\n <input\n aria-label=\"control\"\n type=\"checkbox\"\n checked={selected}\n onChange={(e) => {\n e.stopPropagation();\n onToggleSelect();\n }}\n onClick={(e) => e.stopPropagation()}\n className=\"size-4 rounded border-border-default cursor-pointer\"\n />\n </div>\n )}\n <div className=\"flex-shrink-0 size-9 sm:size-10 rounded-lg bg-[var(--color-accent-soft)] flex items-center justify-center\">\n <Terminal className=\"size-4 sm:size-5 text-action-primary-bg\" />\n </div>\n <div className=\"min-w-0 flex-1\">\n <div className=\"flex items-center gap-1.5 sm:gap-2 flex-wrap\">\n <h3 className=\"font-semibold text-text-primary truncate max-w-[120px] sm:max-w-none text-sm sm:text-base tracking-tight\">\n {session.name}\n </h3>\n <span className=\"text-base sm:text-lg flex-shrink-0\">\n {sessionTypeIcons[session.type] || '💻'}\n </span>\n </div>\n <p className=\"text-xs sm:text-sm text-text-secondary\">{session.type}</p>\n {session.agent && (\n <div className=\"mt-1.5 flex items-center gap-1 flex-wrap\">\n <AgentInfoPopover agent={session.agent} />\n {session.agent.target && <TargetInfoPopover target={session.agent.target} />}\n </div>\n )}\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 <SessionStatusBadge status={session.status} />\n {(onStart || onStop || onRestart || onTerminate) && (\n <div className=\"relative\">\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation();\n setShowMenu(!showMenu);\n }}\n className=\"inline-flex size-8 items-center justify-center rounded-button text-text-secondary hover:bg-bg-sunken hover:text-text-primary transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus\"\n aria-label={tr('vibecontrols.sessions.card.actions', 'Session actions')}\n >\n <MoreVertical className=\"size-4\" aria-hidden=\"true\" />\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.5 w-44 bg-bg-elevated border border-border-subtle rounded-card shadow-elevation-3 z-10 overflow-hidden py-1\"\n >\n {isStopped && onStart && (\n <motion.button\n whileTap={{ scale: 0.97 }}\n onClick={(e) => {\n e.stopPropagation();\n onStart(session);\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 transition-colors\"\n >\n <Play className=\"size-4\" />\n {tr('vibecontrols.sessions.card.start', 'Start')}\n </motion.button>\n )}\n {isRunning && onStop && (\n <motion.button\n whileTap={{ scale: 0.97 }}\n onClick={(e) => {\n e.stopPropagation();\n onStop(session);\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 transition-colors\"\n >\n <Square className=\"size-4\" />\n {tr('vibecontrols.sessions.card.stop', 'Stop')}\n </motion.button>\n )}\n {isRunning && onRestart && (\n <motion.button\n whileTap={{ scale: 0.97 }}\n onClick={(e) => {\n e.stopPropagation();\n onRestart(session);\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 transition-colors\"\n >\n <RotateCcw className=\"size-4\" />\n {tr('vibecontrols.sessions.card.restart', 'Restart')}\n </motion.button>\n )}\n {isRunning && onOpenTerminal && (\n <motion.button\n whileTap={{ scale: 0.97 }}\n onClick={(e) => {\n e.stopPropagation();\n onOpenTerminal(session);\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 transition-colors\"\n >\n <Terminal className=\"size-4\" />\n {tr('vibecontrols.sessions.card.openTerminal', 'Open Terminal')}\n </motion.button>\n )}\n {isRunning && onShare && (\n <motion.button\n whileTap={{ scale: 0.97 }}\n onClick={(e) => {\n e.stopPropagation();\n onShare(session);\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 transition-colors\"\n >\n <Share2 className=\"size-4\" />\n {tr('vibecontrols.sessions.card.shareSession', 'Share Session')}\n </motion.button>\n )}\n {onTerminate && (\n <motion.button\n whileTap={{ scale: 0.97 }}\n onClick={(e) => {\n e.stopPropagation();\n onTerminate(session);\n setShowMenu(false);\n }}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-sm text-status-error-text hover:bg-status-error-bg-subtle transition-colors\"\n >\n <Trash2 className=\"size-4\" />\n {tr('vibecontrols.sessions.card.terminate', 'Terminate')}\n </motion.button>\n )}\n </motion.div>\n )}\n </AnimatePresence>\n </div>\n )}\n </div>\n </div>\n\n {/* Provider-native session name (e.g. tmux session name like\n `vibe-051c9c62`). Surfaced so users can recognise their session\n if they ever need to attach from a CLI (`tmux attach -t …`)\n and so reconnect-after-restart flows are obvious. */}\n {(() => {\n const meta = (session.metadata as Record<string, unknown> | null) || {};\n const externalName =\n (typeof meta.tmuxSessionName === 'string' && meta.tmuxSessionName) ||\n (typeof meta.weztermWorkspace === 'string' && meta.weztermWorkspace) ||\n (typeof meta.zellijSessionName === 'string' && meta.zellijSessionName) ||\n (typeof meta.externalSessionName === 'string' && meta.externalSessionName) ||\n session.providerSessionName ||\n null;\n return externalName ? (\n <div\n className=\"mt-3 text-[10px] font-mono text-text-muted\"\n title={`Provider session: ${externalName}`}\n data-testid=\"session-card-external-name\"\n >\n <span className=\"opacity-70\">{session.type.toLowerCase()}: </span>\n <span className=\"break-all\">{externalName}</span>\n </div>\n ) : null;\n })()}\n\n {session.command && (\n <div className=\"mt-4 text-xs font-mono bg-bg-sunken/60 px-2 py-1.5 rounded truncate text-text-secondary\">\n $ {session.command}\n </div>\n )}\n\n {session.lastOutput && (\n <div className=\"mt-2 text-xs font-mono bg-bg-sunken/60 text-status-success-text px-2 py-1.5 rounded truncate\">\n {session.lastOutput}\n </div>\n )}\n\n <div className=\"mt-4 flex items-center justify-between text-sm text-text-secondary\">\n <div className=\"flex items-center gap-2\">\n <Clock className=\"size-4\" />\n <span className=\"tabular-nums\">{formatTime(session.updatedAt)}</span>\n </div>\n {session.pid && <span className=\"text-xs font-mono tabular-nums\">PID: {session.pid}</span>}\n </div>\n </motion.div>\n );\n};\n"],"mappings":"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"}
@@ -4,31 +4,31 @@ import { jsx as t } from "react/jsx-runtime";
4
4
  var n = {
5
5
  PENDING: {
6
6
  label: "Pending",
7
- color: "bg-status-info-bg text-status-info-text border-status-info-border"
7
+ color: "bg-bg-sunken text-text-secondary border-border-subtle"
8
8
  },
9
9
  STARTING: {
10
10
  label: "Starting",
11
- color: "bg-status-info-bg text-status-info-text border-status-info-border"
11
+ color: "bg-bg-sunken text-text-secondary border-border-subtle"
12
12
  },
13
13
  RUNNING: {
14
14
  label: "Running",
15
- color: "bg-status-success-bg text-status-success-text border-status-success-border"
15
+ color: "bg-status-success-bg-subtle text-status-success-text border-status-success-border"
16
16
  },
17
17
  STOPPING: {
18
18
  label: "Stopping",
19
- color: "bg-status-warning-bg text-status-warning-text border-status-warning-border"
19
+ color: "bg-status-warning-bg-subtle text-status-warning-text border-status-warning-border"
20
20
  },
21
21
  STOPPED: {
22
22
  label: "Stopped",
23
- color: "bg-bg-sunken text-text-secondary border-border-default"
23
+ color: "bg-bg-sunken text-text-secondary border-border-subtle"
24
24
  },
25
25
  ERROR: {
26
26
  label: "Error",
27
- color: "bg-status-error-bg text-status-error-text border-status-error-border"
27
+ color: "bg-status-error-bg-subtle text-status-error-text border-status-error-border"
28
28
  },
29
29
  TERMINATED: {
30
30
  label: "Terminated",
31
- color: "bg-bg-sunken text-text-secondary border-border-default"
31
+ color: "bg-bg-sunken text-text-secondary border-border-subtle"
32
32
  }
33
33
  }, r = ({ status: r, className: i = "" }) => {
34
34
  let { t: a } = e(), o = n[r];
@@ -1 +1 @@
1
- {"version":3,"file":"SessionStatusBadge.js","names":[],"sources":["../../../src/components/sessions/SessionStatusBadge.tsx"],"sourcesContent":["import { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport type { FC } from 'react';\nimport type { SessionStatus } from './types';\n\nexport interface SessionStatusBadgeProps {\n status: SessionStatus;\n className?: string;\n}\n\nconst statusConfig: Record<SessionStatus, { label: string; color: string }> = {\n PENDING: {\n label: 'Pending',\n color: 'bg-status-info-bg text-status-info-text border-status-info-border',\n },\n STARTING: {\n label: 'Starting',\n color: 'bg-status-info-bg text-status-info-text border-status-info-border',\n },\n RUNNING: {\n label: 'Running',\n color: 'bg-status-success-bg text-status-success-text border-status-success-border',\n },\n STOPPING: {\n label: 'Stopping',\n color: 'bg-status-warning-bg text-status-warning-text border-status-warning-border',\n },\n STOPPED: {\n label: 'Stopped',\n color: 'bg-bg-sunken text-text-secondary border-border-default',\n },\n ERROR: {\n label: 'Error',\n color: 'bg-status-error-bg text-status-error-text border-status-error-border',\n },\n TERMINATED: {\n label: 'Terminated',\n color: 'bg-bg-sunken text-text-secondary border-border-default',\n },\n};\n\nexport const SessionStatusBadge: FC<SessionStatusBadgeProps> = ({ status, className = '' }) => {\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\n return (\n <span\n className={`inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium border transition-colors duration-150 ${config.color} ${className}`}\n >\n {config.label}\n </span>\n );\n};\n"],"mappings":";;;AASA,IAAM,IAAwE;CAC5E,SAAS;EACP,OAAO;EACP,OAAO;EACR;CACD,UAAU;EACR,OAAO;EACP,OAAO;EACR;CACD,SAAS;EACP,OAAO;EACP,OAAO;EACR;CACD,UAAU;EACR,OAAO;EACP,OAAO;EACR;CACD,SAAS;EACP,OAAO;EACP,OAAO;EACR;CACD,OAAO;EACL,OAAO;EACP,OAAO;EACR;CACD,YAAY;EACV,OAAO;EACP,OAAO;EACR;CACF,EAEY,KAAmD,EAAE,WAAQ,eAAY,SAAS;CAC7F,IAAM,EAAE,SAAM,GAAS,EAKjB,IAAS,EAAa;AAE5B,QACE,kBAAC,QAAD;EACE,WAAW,iHAAiH,EAAO,MAAM,GAAG;YAE3I,EAAO;EACH,CAAA"}
1
+ {"version":3,"file":"SessionStatusBadge.js","names":[],"sources":["../../../src/components/sessions/SessionStatusBadge.tsx"],"sourcesContent":["import { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport type { FC } from 'react';\nimport type { SessionStatus } from './types';\n\nexport interface SessionStatusBadgeProps {\n status: SessionStatus;\n className?: string;\n}\n\n// Subtle-recipe pills (tinted background + same-hue saturated text + matching\n// border) so they pass WCAG AA in every theme and stay visually consistent with\n// the shared StatusBadge. Transient states (pending/starting) read NEUTRAL —\n// loud blue clashes with the purple brand accent.\nconst statusConfig: Record<SessionStatus, { label: string; color: string }> = {\n PENDING: {\n label: 'Pending',\n color: 'bg-bg-sunken text-text-secondary border-border-subtle',\n },\n STARTING: {\n label: 'Starting',\n color: 'bg-bg-sunken text-text-secondary border-border-subtle',\n },\n RUNNING: {\n label: 'Running',\n color: 'bg-status-success-bg-subtle text-status-success-text border-status-success-border',\n },\n STOPPING: {\n label: 'Stopping',\n color: 'bg-status-warning-bg-subtle text-status-warning-text border-status-warning-border',\n },\n STOPPED: {\n label: 'Stopped',\n color: 'bg-bg-sunken text-text-secondary border-border-subtle',\n },\n ERROR: {\n label: 'Error',\n color: 'bg-status-error-bg-subtle text-status-error-text border-status-error-border',\n },\n TERMINATED: {\n label: 'Terminated',\n color: 'bg-bg-sunken text-text-secondary border-border-subtle',\n },\n};\n\nexport const SessionStatusBadge: FC<SessionStatusBadgeProps> = ({ status, className = '' }) => {\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\n return (\n <span\n className={`inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium border transition-colors duration-150 ${config.color} ${className}`}\n >\n {config.label}\n </span>\n );\n};\n"],"mappings":";;;AAaA,IAAM,IAAwE;CAC5E,SAAS;EACP,OAAO;EACP,OAAO;EACR;CACD,UAAU;EACR,OAAO;EACP,OAAO;EACR;CACD,SAAS;EACP,OAAO;EACP,OAAO;EACR;CACD,UAAU;EACR,OAAO;EACP,OAAO;EACR;CACD,SAAS;EACP,OAAO;EACP,OAAO;EACR;CACD,OAAO;EACL,OAAO;EACP,OAAO;EACR;CACD,YAAY;EACV,OAAO;EACP,OAAO;EACR;CACF,EAEY,KAAmD,EAAE,WAAQ,eAAY,SAAS;CAC7F,IAAM,EAAE,SAAM,GAAS,EAKjB,IAAS,EAAa;AAE5B,QACE,kBAAC,QAAD;EACE,WAAW,iHAAiH,EAAO,MAAM,GAAG;YAE3I,EAAO;EACH,CAAA"}
@@ -53,14 +53,14 @@ function p({ label: p, className: m, style: h, onChange: g, compact: _ = !1 }) {
53
53
  className: m,
54
54
  style: h,
55
55
  children: /* @__PURE__ */ d("div", {
56
- className: "inline-flex items-center gap-2 rounded-md border border-border-default bg-bg-sunken px-3 py-1.5 text-xs text-text-secondary",
56
+ className: "inline-flex items-center gap-2 rounded-input border border-border-subtle bg-bg-sunken px-3 py-1.5 text-xs text-text-secondary",
57
57
  children: [/* @__PURE__ */ u(l, { className: "size-3.5" }), "Loading agents…"]
58
58
  })
59
59
  }) : S.length === 0 ? /* @__PURE__ */ u("div", {
60
60
  className: m,
61
61
  style: h,
62
62
  children: /* @__PURE__ */ d("div", {
63
- className: "inline-flex items-center gap-2 rounded-md border border-border-default bg-bg-sunken px-3 py-1.5 text-xs text-text-secondary",
63
+ className: "inline-flex items-center gap-2 rounded-input border border-border-subtle bg-bg-sunken px-3 py-1.5 text-xs text-text-secondary",
64
64
  children: [/* @__PURE__ */ u(l, { className: "size-3.5" }), "No agents yet — add one from the Agents page"]
65
65
  })
66
66
  }) : /* @__PURE__ */ d("div", {
@@ -81,7 +81,7 @@ function p({ label: p, className: m, style: h, onChange: g, compact: _ = !1 }) {
81
81
  /* @__PURE__ */ u("select", {
82
82
  value: C?.id ?? "",
83
83
  onChange: w,
84
- className: `appearance-none rounded-md border border-border-default bg-bg-surface pl-7 pr-8 text-text-primary focus:outline-none focus:ring-2 focus:ring-action-primary-bg/40 ${_ ? "py-1 text-xs" : "py-2 text-sm"}`,
84
+ className: `appearance-none rounded-input border border-border-subtle bg-bg-surface pl-7 pr-8 text-text-primary transition-colors hover:border-border-strong focus:outline-none focus:ring-1 focus:ring-[var(--color-focus-ring)] focus:border-border-strong ${_ ? "py-1 text-xs" : "py-2 text-sm"}`,
85
85
  "data-testid": "agent-selector",
86
86
  children: S.map((e) => /* @__PURE__ */ d("option", {
87
87
  value: e.id,
@@ -109,7 +109,7 @@ function p({ label: p, className: m, style: h, onChange: g, compact: _ = !1 }) {
109
109
  children: [/* @__PURE__ */ u("select", {
110
110
  value: E,
111
111
  onChange: O,
112
- className: `appearance-none rounded-md border border-border-default bg-bg-surface pl-3 pr-7 text-text-primary focus:outline-none focus:ring-2 focus:ring-action-primary-bg/40 font-mono ${_ ? "py-1 text-xs" : "py-2 text-sm"}`,
112
+ className: `appearance-none rounded-input border border-border-subtle bg-bg-surface pl-3 pr-7 text-text-primary transition-colors hover:border-border-strong focus:outline-none focus:ring-1 focus:ring-[var(--color-focus-ring)] focus:border-border-strong font-mono ${_ ? "py-1 text-xs" : "py-2 text-sm"}`,
113
113
  "data-testid": "agent-profile-selector",
114
114
  "aria-label": "Active profile",
115
115
  children: T.map((e) => /* @__PURE__ */ d("option", {
@@ -1 +1 @@
1
- {"version":3,"file":"AgentSelector.js","names":[],"sources":["../../../src/components/shared/AgentSelector.tsx"],"sourcesContent":["/**\n * AgentSelector — dropdown of workspace agents with selection persisted to the\n * VibeControls Zustand store. Used on the AI page and anywhere else a user\n * needs to pick \"the active agent\" explicitly rather than rely on auto-pick.\n *\n * The selector shows active agents first, then inactive/disconnected ones,\n * and surfaces the agent's tunnel/heartbeat state via a colored dot so the\n * user knows at a glance which one is actually running.\n */\n\nimport { useCallback, useEffect, useMemo, type CSSProperties } from 'react';\nimport { Check, ChevronDown, Server } from 'lucide-react';\nimport { useVibeControlsStore } from '@/store/vibeControlsStore';\nimport { useCachedInitialData } from '@/hooks/useCachedInitialData';\nimport { useActiveAgentProfile } from '@/hooks/useActiveAgentProfile';\nimport { getAgentStatus } from '@/components/agents/agentStatus';\n\nexport interface AgentOption {\n id: string;\n name: string;\n hostname: string;\n isActive: boolean;\n lastHeartbeat?: string | null;\n tunnelUrl?: string | null;\n tunnelStatus?: string | null;\n}\n\ninterface AgentSelectorProps {\n /** Optional label above the selector. */\n label?: string;\n /** Optional className applied to the outer wrapper. */\n className?: string;\n /** Optional inline style applied to the outer wrapper. */\n style?: CSSProperties;\n /** Fired whenever the user changes the selected agent. */\n onChange?: (agent: AgentOption | null) => void;\n /** Render compact (smaller, no label). */\n compact?: boolean;\n}\n\nfunction statusDotColor(agent: AgentOption): string {\n const status = getAgentStatus(agent);\n if (status === 'OFFLINE') return 'bg-text-placeholder';\n if (status === 'DEGRADED' || !agent.tunnelUrl) return 'bg-status-warning-text';\n return 'bg-status-success-text';\n}\n\nexport function AgentSelector({\n label,\n className,\n style,\n onChange,\n compact = false,\n}: AgentSelectorProps) {\n const { agents, loading } = useCachedInitialData();\n const activeAgentId = useVibeControlsStore((s) => s.activeAgentId);\n const setActiveAgentId = useVibeControlsStore((s) => s.setActiveAgentId);\n\n const options: AgentOption[] = useMemo(() => {\n const sorted = [...agents].sort((a, b) => {\n // ACTIVE + online first; then ACTIVE but no tunnel; then DEGRADED;\n // then OFFLINE. Falls back through getAgentStatus precedence.\n const statusRank = (x: (typeof agents)[number]) => {\n const s = getAgentStatus(x);\n if (s === 'ACTIVE') return 0;\n if (s === 'DEGRADED') return 1;\n return 2;\n };\n const score = (x: (typeof agents)[number]) => statusRank(x) * 2 + (x.tunnelUrl ? 0 : 1);\n if (score(a) !== score(b)) return score(a) - score(b);\n return a.name.localeCompare(b.name);\n });\n return sorted.map((a) => ({\n id: a.id,\n name: a.name,\n hostname: a.hostname,\n isActive: a.isActive,\n lastHeartbeat: a.lastHeartbeat ?? null,\n tunnelUrl: a.tunnelUrl ?? null,\n tunnelStatus: a.tunnelStatus ?? null,\n }));\n }, [agents]);\n\n const selected = useMemo<AgentOption | null>(() => {\n if (activeAgentId) {\n const match = options.find((a) => a.id === activeAgentId);\n if (match) return match;\n }\n // Auto-pick the first healthy agent the first time.\n return options.find((a) => getAgentStatus(a) === 'ACTIVE' && a.tunnelUrl) ?? options[0] ?? null;\n }, [activeAgentId, options]);\n\n // Commit the auto-picked agent into the store so downstream consumers can\n // read it directly without running the same \"first healthy\" logic.\n useEffect(() => {\n if (selected && activeAgentId !== selected.id) {\n setActiveAgentId(selected.id);\n }\n }, [selected, activeAgentId, setActiveAgentId]);\n\n const handleChange = useCallback(\n (e: React.ChangeEvent<HTMLSelectElement>) => {\n const id = e.target.value || null;\n setActiveAgentId(id);\n onChange?.(id ? (options.find((o) => o.id === id) ?? null) : null);\n },\n [options, setActiveAgentId, onChange]\n );\n\n // Profile sub-dropdown — Phase 5B (multi-profile-per-daemon).\n // Resolves the AgentProfile rows for the currently-selected agent and\n // exposes a switcher next to the agent dropdown. Hidden until profiles\n // beyond the implicit \"default\" exist so single-profile setups stay clean.\n const {\n profiles: agentProfiles,\n active: activeProfile,\n setActive: setActiveProfile,\n } = useActiveAgentProfile(selected?.id ?? null);\n const handleProfileChange = useCallback(\n (e: React.ChangeEvent<HTMLSelectElement>) => {\n setActiveProfile(e.target.value);\n },\n [setActiveProfile]\n );\n\n if (loading && options.length === 0) {\n return (\n <div className={className} style={style}>\n <div className=\"inline-flex items-center gap-2 rounded-md border border-border-default bg-bg-sunken px-3 py-1.5 text-xs text-text-secondary\">\n <Server className=\"size-3.5\" />\n Loading agents…\n </div>\n </div>\n );\n }\n\n if (options.length === 0) {\n return (\n <div className={className} style={style}>\n <div className=\"inline-flex items-center gap-2 rounded-md border border-border-default bg-bg-sunken px-3 py-1.5 text-xs text-text-secondary\">\n <Server className=\"size-3.5\" />\n No agents yet — add one from the Agents page\n </div>\n </div>\n );\n }\n\n return (\n <div className={className} style={style}>\n {label && !compact && (\n <label className=\"block text-xs font-medium text-text-secondary mb-1\">{label}</label>\n )}\n <div className=\"relative inline-flex items-center\">\n {selected && (\n <span\n className={`absolute left-2.5 z-10 size-2 rounded-full ${statusDotColor(selected)}`}\n aria-hidden\n />\n )}\n <select\n value={selected?.id ?? ''}\n onChange={handleChange}\n className={`appearance-none rounded-md border border-border-default bg-bg-surface pl-7 pr-8 text-text-primary focus:outline-none focus:ring-2 focus:ring-action-primary-bg/40 ${compact ? 'py-1 text-xs' : 'py-2 text-sm'}`}\n data-testid=\"agent-selector\"\n >\n {options.map((agent) => (\n <option key={agent.id} value={agent.id}>\n {agent.name} ({agent.hostname})\n {(() => {\n const s = getAgentStatus(agent);\n if (s === 'ACTIVE') return '';\n if (s === 'DEGRADED') return ' · degraded';\n return ' · offline';\n })()}\n </option>\n ))}\n </select>\n <ChevronDown className=\"pointer-events-none absolute right-2 size-3.5 text-text-secondary\" />\n {selected?.tunnelUrl && (\n <Check\n className=\"pointer-events-none absolute -right-5 size-3.5 text-status-success-text\"\n aria-label=\"Tunnel reachable\"\n />\n )}\n </div>\n {selected && agentProfiles.length > 1 && (\n <div className=\"relative inline-flex items-center ml-2\">\n <select\n value={activeProfile}\n onChange={handleProfileChange}\n className={`appearance-none rounded-md border border-border-default bg-bg-surface pl-3 pr-7 text-text-primary focus:outline-none focus:ring-2 focus:ring-action-primary-bg/40 font-mono ${compact ? 'py-1 text-xs' : 'py-2 text-sm'}`}\n data-testid=\"agent-profile-selector\"\n aria-label=\"Active profile\"\n >\n {agentProfiles.map((p) => (\n <option key={p.id} value={p.name}>\n {p.name}\n {p.isDefault ? ' (default)' : ''}\n </option>\n ))}\n </select>\n <ChevronDown className=\"pointer-events-none absolute right-2 size-3.5 text-text-secondary\" />\n </div>\n )}\n </div>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"AgentSelector.js","names":[],"sources":["../../../src/components/shared/AgentSelector.tsx"],"sourcesContent":["/**\n * AgentSelector — dropdown of workspace agents with selection persisted to the\n * VibeControls Zustand store. Used on the AI page and anywhere else a user\n * needs to pick \"the active agent\" explicitly rather than rely on auto-pick.\n *\n * The selector shows active agents first, then inactive/disconnected ones,\n * and surfaces the agent's tunnel/heartbeat state via a colored dot so the\n * user knows at a glance which one is actually running.\n */\n\nimport { useCallback, useEffect, useMemo, type CSSProperties } from 'react';\nimport { Check, ChevronDown, Server } from 'lucide-react';\nimport { useVibeControlsStore } from '@/store/vibeControlsStore';\nimport { useCachedInitialData } from '@/hooks/useCachedInitialData';\nimport { useActiveAgentProfile } from '@/hooks/useActiveAgentProfile';\nimport { getAgentStatus } from '@/components/agents/agentStatus';\n\nexport interface AgentOption {\n id: string;\n name: string;\n hostname: string;\n isActive: boolean;\n lastHeartbeat?: string | null;\n tunnelUrl?: string | null;\n tunnelStatus?: string | null;\n}\n\ninterface AgentSelectorProps {\n /** Optional label above the selector. */\n label?: string;\n /** Optional className applied to the outer wrapper. */\n className?: string;\n /** Optional inline style applied to the outer wrapper. */\n style?: CSSProperties;\n /** Fired whenever the user changes the selected agent. */\n onChange?: (agent: AgentOption | null) => void;\n /** Render compact (smaller, no label). */\n compact?: boolean;\n}\n\nfunction statusDotColor(agent: AgentOption): string {\n const status = getAgentStatus(agent);\n if (status === 'OFFLINE') return 'bg-text-placeholder';\n if (status === 'DEGRADED' || !agent.tunnelUrl) return 'bg-status-warning-text';\n return 'bg-status-success-text';\n}\n\nexport function AgentSelector({\n label,\n className,\n style,\n onChange,\n compact = false,\n}: AgentSelectorProps) {\n const { agents, loading } = useCachedInitialData();\n const activeAgentId = useVibeControlsStore((s) => s.activeAgentId);\n const setActiveAgentId = useVibeControlsStore((s) => s.setActiveAgentId);\n\n const options: AgentOption[] = useMemo(() => {\n const sorted = [...agents].sort((a, b) => {\n // ACTIVE + online first; then ACTIVE but no tunnel; then DEGRADED;\n // then OFFLINE. Falls back through getAgentStatus precedence.\n const statusRank = (x: (typeof agents)[number]) => {\n const s = getAgentStatus(x);\n if (s === 'ACTIVE') return 0;\n if (s === 'DEGRADED') return 1;\n return 2;\n };\n const score = (x: (typeof agents)[number]) => statusRank(x) * 2 + (x.tunnelUrl ? 0 : 1);\n if (score(a) !== score(b)) return score(a) - score(b);\n return a.name.localeCompare(b.name);\n });\n return sorted.map((a) => ({\n id: a.id,\n name: a.name,\n hostname: a.hostname,\n isActive: a.isActive,\n lastHeartbeat: a.lastHeartbeat ?? null,\n tunnelUrl: a.tunnelUrl ?? null,\n tunnelStatus: a.tunnelStatus ?? null,\n }));\n }, [agents]);\n\n const selected = useMemo<AgentOption | null>(() => {\n if (activeAgentId) {\n const match = options.find((a) => a.id === activeAgentId);\n if (match) return match;\n }\n // Auto-pick the first healthy agent the first time.\n return options.find((a) => getAgentStatus(a) === 'ACTIVE' && a.tunnelUrl) ?? options[0] ?? null;\n }, [activeAgentId, options]);\n\n // Commit the auto-picked agent into the store so downstream consumers can\n // read it directly without running the same \"first healthy\" logic.\n useEffect(() => {\n if (selected && activeAgentId !== selected.id) {\n setActiveAgentId(selected.id);\n }\n }, [selected, activeAgentId, setActiveAgentId]);\n\n const handleChange = useCallback(\n (e: React.ChangeEvent<HTMLSelectElement>) => {\n const id = e.target.value || null;\n setActiveAgentId(id);\n onChange?.(id ? (options.find((o) => o.id === id) ?? null) : null);\n },\n [options, setActiveAgentId, onChange]\n );\n\n // Profile sub-dropdown — Phase 5B (multi-profile-per-daemon).\n // Resolves the AgentProfile rows for the currently-selected agent and\n // exposes a switcher next to the agent dropdown. Hidden until profiles\n // beyond the implicit \"default\" exist so single-profile setups stay clean.\n const {\n profiles: agentProfiles,\n active: activeProfile,\n setActive: setActiveProfile,\n } = useActiveAgentProfile(selected?.id ?? null);\n const handleProfileChange = useCallback(\n (e: React.ChangeEvent<HTMLSelectElement>) => {\n setActiveProfile(e.target.value);\n },\n [setActiveProfile]\n );\n\n if (loading && options.length === 0) {\n return (\n <div className={className} style={style}>\n <div className=\"inline-flex items-center gap-2 rounded-input border border-border-subtle bg-bg-sunken px-3 py-1.5 text-xs text-text-secondary\">\n <Server className=\"size-3.5\" />\n Loading agents…\n </div>\n </div>\n );\n }\n\n if (options.length === 0) {\n return (\n <div className={className} style={style}>\n <div className=\"inline-flex items-center gap-2 rounded-input border border-border-subtle bg-bg-sunken px-3 py-1.5 text-xs text-text-secondary\">\n <Server className=\"size-3.5\" />\n No agents yet — add one from the Agents page\n </div>\n </div>\n );\n }\n\n return (\n <div className={className} style={style}>\n {label && !compact && (\n <label className=\"block text-xs font-medium text-text-secondary mb-1\">{label}</label>\n )}\n <div className=\"relative inline-flex items-center\">\n {selected && (\n <span\n className={`absolute left-2.5 z-10 size-2 rounded-full ${statusDotColor(selected)}`}\n aria-hidden\n />\n )}\n <select\n value={selected?.id ?? ''}\n onChange={handleChange}\n className={`appearance-none rounded-input border border-border-subtle bg-bg-surface pl-7 pr-8 text-text-primary transition-colors hover:border-border-strong focus:outline-none focus:ring-1 focus:ring-[var(--color-focus-ring)] focus:border-border-strong ${compact ? 'py-1 text-xs' : 'py-2 text-sm'}`}\n data-testid=\"agent-selector\"\n >\n {options.map((agent) => (\n <option key={agent.id} value={agent.id}>\n {agent.name} ({agent.hostname})\n {(() => {\n const s = getAgentStatus(agent);\n if (s === 'ACTIVE') return '';\n if (s === 'DEGRADED') return ' · degraded';\n return ' · offline';\n })()}\n </option>\n ))}\n </select>\n <ChevronDown className=\"pointer-events-none absolute right-2 size-3.5 text-text-secondary\" />\n {selected?.tunnelUrl && (\n <Check\n className=\"pointer-events-none absolute -right-5 size-3.5 text-status-success-text\"\n aria-label=\"Tunnel reachable\"\n />\n )}\n </div>\n {selected && agentProfiles.length > 1 && (\n <div className=\"relative inline-flex items-center ml-2\">\n <select\n value={activeProfile}\n onChange={handleProfileChange}\n className={`appearance-none rounded-input border border-border-subtle bg-bg-surface pl-3 pr-7 text-text-primary transition-colors hover:border-border-strong focus:outline-none focus:ring-1 focus:ring-[var(--color-focus-ring)] focus:border-border-strong font-mono ${compact ? 'py-1 text-xs' : 'py-2 text-sm'}`}\n data-testid=\"agent-profile-selector\"\n aria-label=\"Active profile\"\n >\n {agentProfiles.map((p) => (\n <option key={p.id} value={p.name}>\n {p.name}\n {p.isDefault ? ' (default)' : ''}\n </option>\n ))}\n </select>\n <ChevronDown className=\"pointer-events-none absolute right-2 size-3.5 text-text-secondary\" />\n </div>\n )}\n </div>\n );\n}\n"],"mappings":"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"}
@@ -57,7 +57,7 @@ function b({ variant: b = "compact", dismissible: x = !0, dismissKey: S = "vibec
57
57
  height: 0
58
58
  },
59
59
  transition: N,
60
- className: `relative flex flex-col items-start gap-3 px-4 py-3 bg-accent-purple-subtle border border-accent-purple/20 rounded-lg sm:flex-row sm:items-center ${C}`,
60
+ className: `relative flex flex-col items-start gap-3 px-4 py-3 bg-accent-purple-subtle border border-accent-purple/20 rounded-card sm:flex-row sm:items-center ${C}`,
61
61
  children: [
62
62
  /* @__PURE__ */ h(c, { className: "size-5 text-accent-purple flex-shrink-0" }),
63
63
  /* @__PURE__ */ g("div", {
@@ -68,7 +68,7 @@ function b({ variant: b = "compact", dismissible: x = !0, dismissKey: S = "vibec
68
68
  children: w("vibecontrols.shared.agentSetupBanner.installPrompt", "Install the VibeControls Agent CLI to get started")
69
69
  }),
70
70
  /* @__PURE__ */ g("code", {
71
- className: "inline-flex min-w-0 max-w-full items-center gap-1.5 rounded border border-border-default bg-bg-sunken px-2.5 py-1 font-mono text-xs text-text-primary transition-colors hover:bg-bg-elevated",
71
+ className: "inline-flex min-w-0 max-w-full items-center gap-1.5 rounded border border-border-subtle bg-bg-sunken px-2.5 py-1 font-mono text-xs text-text-primary transition-colors hover:bg-bg-elevated cursor-pointer",
72
72
  onClick: () => j(E),
73
73
  title: w("vibecontrols.shared.agentSetupBanner.clickToCopy", "Click to copy"),
74
74
  children: [
@@ -117,7 +117,7 @@ function b({ variant: b = "compact", dismissible: x = !0, dismissKey: S = "vibec
117
117
  height: 0
118
118
  },
119
119
  transition: N,
120
- className: `relative border border-accent-purple/20 bg-accent-purple-subtle rounded-lg overflow-hidden ${C}`,
120
+ className: `relative border border-accent-purple/20 bg-accent-purple-subtle rounded-card overflow-hidden ${C}`,
121
121
  children: [x && /* @__PURE__ */ h("button", {
122
122
  type: "button",
123
123
  onClick: M,