@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
@@ -1 +1 @@
1
- {"version":3,"file":"PluginsCatalogPage.js","names":[],"sources":["../../src/pages/PluginsCatalogPage.tsx"],"sourcesContent":["import { useMemo, useState, useCallback } from 'react';\nimport { Check, ChevronDown, Copy, Package, Plug, Search, X } from 'lucide-react';\nimport { MotionPage } from '@burdenoff/fe-libs/shared/motion';\nimport { PageContainer, Card } from '@burdenoff/fe-libs/ui';\nimport { PLUGIN_CATALOG, type PluginMeta } from '@/constants/pluginCatalog';\nimport { useTr } from '@/shared/hooks/useTr';\n\ntype PluginType = 'Meta' | 'Provider' | 'Plugin';\n\ninterface CategoryOption {\n value: string;\n labelKey: string;\n labelFallback: string;\n}\n\nconst CATEGORY_OPTIONS: CategoryOption[] = [\n { value: 'all', labelKey: 'vibecontrols.plugins.category.all', labelFallback: 'All' },\n { value: 'ai-core', labelKey: 'vibecontrols.plugins.category.aiCore', labelFallback: 'AI Core' },\n {\n value: 'ai-provider',\n labelKey: 'vibecontrols.plugins.category.aiProvider',\n labelFallback: 'AI Providers',\n },\n {\n value: 'plan-core',\n labelKey: 'vibecontrols.plugins.category.planCore',\n labelFallback: 'Plan Core',\n },\n {\n value: 'plan-provider',\n labelKey: 'vibecontrols.plugins.category.planProvider',\n labelFallback: 'Plan Providers',\n },\n {\n value: 'session-core',\n labelKey: 'vibecontrols.plugins.category.sessionCore',\n labelFallback: 'Session Core',\n },\n {\n value: 'session-provider',\n labelKey: 'vibecontrols.plugins.category.sessionProvider',\n labelFallback: 'Session Providers',\n },\n { value: 'tunnel', labelKey: 'vibecontrols.plugins.category.tunnel', labelFallback: 'Tunnel' },\n { value: 'vcs', labelKey: 'vibecontrols.plugins.category.vcs', labelFallback: 'VCS' },\n {\n value: 'api-playground',\n labelKey: 'vibecontrols.plugins.category.apiPlayground',\n labelFallback: 'API Playground',\n },\n {\n value: 'gitops-core',\n labelKey: 'vibecontrols.plugins.category.gitopsCore',\n labelFallback: 'GitOps Core',\n },\n {\n value: 'gitops-provider',\n labelKey: 'vibecontrols.plugins.category.gitopsProvider',\n labelFallback: 'GitOps Providers',\n },\n {\n value: 'security-core',\n labelKey: 'vibecontrols.plugins.category.securityCore',\n labelFallback: 'Security Core',\n },\n {\n value: 'security-provider',\n labelKey: 'vibecontrols.plugins.category.securityProvider',\n labelFallback: 'Security Providers',\n },\n { value: 'tool', labelKey: 'vibecontrols.plugins.category.tool', labelFallback: 'Tools' },\n];\n\nfunction derivePluginType(category: PluginMeta['category']): PluginType {\n if (category.endsWith('-core')) return 'Meta';\n if (category.endsWith('-provider')) return 'Provider';\n return 'Plugin';\n}\n\nfunction typeBadgeClasses(type: PluginType): string {\n switch (type) {\n case 'Meta':\n return 'bg-accent-purple-subtle text-accent-purple border-accent-purple/30';\n case 'Provider':\n return 'bg-status-info-bg text-status-info-text border-status-info-border';\n default:\n return 'bg-bg-sunken text-text-secondary border-border-default';\n }\n}\n\ninterface CopyButtonProps {\n text: string;\n ariaLabel: string;\n}\n\nfunction CopyButton({ text, ariaLabel }: CopyButtonProps) {\n const [copied, setCopied] = useState(false);\n const handleCopy = useCallback(async () => {\n try {\n if (typeof navigator !== 'undefined' && navigator.clipboard) {\n await navigator.clipboard.writeText(text);\n }\n setCopied(true);\n window.setTimeout(() => setCopied(false), 1500);\n } catch {\n setCopied(false);\n }\n }, [text]);\n\n return (\n <button\n type=\"button\"\n onClick={handleCopy}\n aria-label={ariaLabel}\n className=\"inline-flex items-center justify-center size-7 rounded border border-border-default bg-bg-surface text-text-secondary hover:text-text-primary hover:bg-bg-elevated transition-colors flex-shrink-0\"\n >\n {copied ? <Check className=\"size-3.5\" /> : <Copy className=\"size-3.5\" />}\n </button>\n );\n}\n\ninterface PluginCardProps {\n plugin: PluginMeta;\n}\n\nfunction PluginCard({ plugin }: PluginCardProps) {\n const tr = useTr();\n const type = derivePluginType(plugin.category);\n const installCommand = `npm install ${plugin.packageName}`;\n const usageSnippet = `import plugin from '${plugin.packageName}';`;\n\n const [showUsage, setShowUsage] = useState(false);\n const [showCommands, setShowCommands] = useState(false);\n\n return (\n <Card className=\"flex flex-col h-full p-0 bg-bg-surface border-border-default\">\n <div className=\"flex flex-col gap-3 p-4\">\n <div className=\"flex items-start justify-between gap-3\">\n <div className=\"min-w-0 flex-1\">\n <h3 className=\"text-base font-semibold text-text-primary truncate\">\n {plugin.displayName}\n </h3>\n <p className=\"text-xs text-text-secondary font-mono truncate mt-1\">\n {plugin.packageName}\n </p>\n </div>\n <span\n className={`inline-flex items-center gap-1 px-2 py-0.5 text-[10px] uppercase tracking-wide font-semibold rounded-full border ${typeBadgeClasses(type)} flex-shrink-0`}\n >\n {type}\n </span>\n </div>\n\n <p className=\"text-sm text-text-secondary leading-snug\">{plugin.description}</p>\n\n <div className=\"mt-1 space-y-1.5\">\n <p className=\"text-[11px] font-medium uppercase tracking-wide text-text-secondary\">\n {tr('vibecontrols.plugins.install', 'Install')}\n </p>\n <div className=\"flex items-center gap-2 rounded-md border border-border-default bg-bg-sunken px-2.5 py-1.5\">\n <code className=\"flex-1 text-xs font-mono text-text-primary truncate\">\n {installCommand}\n </code>\n <CopyButton\n text={installCommand}\n ariaLabel={tr(\n 'vibecontrols.plugins.copyInstall',\n 'Copy install command for {{name}}',\n {\n name: plugin.displayName,\n }\n )}\n />\n </div>\n </div>\n\n <div className=\"space-y-2\">\n <button\n type=\"button\"\n onClick={() => setShowUsage((value) => !value)}\n className=\"w-full flex items-center justify-between gap-2 text-left text-xs font-medium text-text-secondary hover:text-text-primary transition-colors\"\n aria-expanded={showUsage}\n >\n <span>{tr('vibecontrols.plugins.usage', 'Usage')}</span>\n <ChevronDown\n className={`size-3.5 transition-transform ${showUsage ? 'rotate-180' : ''}`}\n />\n </button>\n {showUsage && (\n <div className=\"flex items-start gap-2 rounded-md border border-border-default bg-bg-sunken px-2.5 py-2\">\n <code className=\"flex-1 text-xs font-mono text-text-primary whitespace-pre-wrap break-all\">\n {usageSnippet}\n </code>\n <CopyButton\n text={usageSnippet}\n ariaLabel={tr('vibecontrols.plugins.copyUsage', 'Copy usage snippet for {{name}}', {\n name: plugin.displayName,\n })}\n />\n </div>\n )}\n </div>\n\n <div className=\"space-y-2\">\n <button\n type=\"button\"\n onClick={() => setShowCommands((value) => !value)}\n className=\"w-full flex items-center justify-between gap-2 text-left text-xs font-medium text-text-secondary hover:text-text-primary transition-colors\"\n aria-expanded={showCommands}\n >\n <span>{tr('vibecontrols.plugins.commands', 'Command reference')}</span>\n <ChevronDown\n className={`size-3.5 transition-transform ${showCommands ? 'rotate-180' : ''}`}\n />\n </button>\n {showCommands && (\n <div className=\"rounded-md border border-border-default bg-bg-sunken px-2.5 py-2\">\n <p className=\"text-xs text-text-secondary\">\n {tr(\n 'vibecontrols.plugins.commandsHint',\n 'See the agent admin sub-panel or the plugin README for the documented command list.'\n )}\n </p>\n </div>\n )}\n </div>\n </div>\n </Card>\n );\n}\n\nexport function PluginsCatalogPage() {\n const tr = useTr();\n const [search, setSearch] = useState('');\n const [activeCategory, setActiveCategory] = useState<string>('all');\n\n const pluginList = useMemo(() => {\n return Object.values(PLUGIN_CATALOG).sort((a, b) => a.displayName.localeCompare(b.displayName));\n }, []);\n\n const filteredPlugins = useMemo(() => {\n const term = search.trim().toLowerCase();\n return pluginList.filter((plugin) => {\n if (activeCategory !== 'all' && plugin.category !== activeCategory) return false;\n if (!term) return true;\n const haystack =\n `${plugin.displayName} ${plugin.description} ${plugin.packageName}`.toLowerCase();\n return haystack.includes(term);\n });\n }, [pluginList, search, activeCategory]);\n\n return (\n <PageContainer padded={false}>\n <MotionPage className=\"flex flex-col h-full\">\n {/* Sticky header with search + filter chips */}\n <div className=\"sticky top-0 z-10 border-b border-border-default bg-bg-surface px-4 sm:px-6 pt-4 pb-3 space-y-3\">\n <div className=\"flex items-start gap-3\">\n <Plug className=\"size-6 text-text-primary mt-0.5 flex-shrink-0\" />\n <div className=\"min-w-0 flex-1\">\n <h1 className=\"text-xl font-bold text-text-primary\">\n {tr('vibecontrols.plugins.title', 'Plugins')}\n </h1>\n <p className=\"text-sm text-text-secondary mt-0.5\">\n {tr(\n 'vibecontrols.plugins.description',\n 'Browse and install plugins to extend your VibeControls agents.'\n )}\n </p>\n </div>\n </div>\n\n <div className=\"relative\">\n <Search className=\"absolute left-3 top-1/2 -translate-y-1/2 size-4 text-text-secondary\" />\n <input\n type=\"search\"\n value={search}\n onChange={(event) => setSearch(event.target.value)}\n placeholder={tr(\n 'vibecontrols.plugins.searchPlaceholder',\n 'Search by name, description, or package…'\n )}\n aria-label={tr('vibecontrols.plugins.searchLabel', 'Search plugins')}\n className=\"w-full pl-10 pr-10 py-2 text-sm bg-bg-sunken border border-border-default rounded-md text-text-primary placeholder:text-text-placeholder focus:outline-none focus:ring-2 focus:ring-action-primary-bg focus:border-transparent transition-all\"\n />\n {search && (\n <button\n type=\"button\"\n onClick={() => setSearch('')}\n aria-label={tr('vibecontrols.plugins.clearSearch', 'Clear search')}\n className=\"absolute right-3 top-1/2 -translate-y-1/2 p-0.5 rounded hover:bg-bg-elevated transition-colors\"\n >\n <X className=\"size-4 text-text-secondary\" />\n </button>\n )}\n </div>\n\n <div\n className=\"flex flex-wrap gap-2\"\n role=\"tablist\"\n aria-label={tr('vibecontrols.plugins.categories', 'Plugin categories')}\n >\n {CATEGORY_OPTIONS.map((option) => {\n const isActive = activeCategory === option.value;\n return (\n <button\n key={option.value}\n type=\"button\"\n role=\"tab\"\n aria-selected={isActive}\n onClick={() => setActiveCategory(option.value)}\n className={`px-3 py-1 text-xs rounded-full border transition-colors ${\n isActive\n ? 'bg-action-primary-bg text-action-primary-text border-action-primary-bg'\n : 'bg-bg-surface text-text-secondary border-border-default hover:bg-bg-sunken hover:text-text-primary'\n }`}\n >\n {tr(option.labelKey, option.labelFallback)}\n </button>\n );\n })}\n </div>\n </div>\n\n {/* Grid body */}\n <div className=\"flex-1 px-4 sm:px-6 py-4\">\n {filteredPlugins.length === 0 ? (\n <div className=\"flex flex-col items-center justify-center text-center py-16 text-text-secondary\">\n <Package className=\"size-12 mb-3 opacity-50\" />\n <p className=\"text-sm font-medium text-text-primary\">\n {tr('vibecontrols.plugins.empty.title', 'No matching plugins')}\n </p>\n <p className=\"text-xs mt-1\">\n {tr(\n 'vibecontrols.plugins.empty.description',\n 'Try a different search term or category.'\n )}\n </p>\n </div>\n ) : (\n <div className=\"grid gap-3 grid-cols-1 sm:grid-cols-2 xl:grid-cols-3\">\n {filteredPlugins.map((plugin) => (\n <PluginCard key={plugin.packageName} plugin={plugin} />\n ))}\n </div>\n )}\n </div>\n </MotionPage>\n </PageContainer>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"PluginsCatalogPage.js","names":[],"sources":["../../src/pages/PluginsCatalogPage.tsx"],"sourcesContent":["import { useMemo, useState, useCallback } from 'react';\nimport { Check, ChevronDown, Copy, Package, Plug, Search, X } from 'lucide-react';\nimport { MotionPage } from '@burdenoff/fe-libs/shared/motion';\nimport { PageContainer, Card } from '@burdenoff/fe-libs/ui';\nimport { PLUGIN_CATALOG, type PluginMeta } from '@/constants/pluginCatalog';\nimport { useTr } from '@/shared/hooks/useTr';\n\ntype PluginType = 'Meta' | 'Provider' | 'Plugin';\n\ninterface CategoryOption {\n value: string;\n labelKey: string;\n labelFallback: string;\n}\n\nconst CATEGORY_OPTIONS: CategoryOption[] = [\n { value: 'all', labelKey: 'vibecontrols.plugins.category.all', labelFallback: 'All' },\n { value: 'ai-core', labelKey: 'vibecontrols.plugins.category.aiCore', labelFallback: 'AI Core' },\n {\n value: 'ai-provider',\n labelKey: 'vibecontrols.plugins.category.aiProvider',\n labelFallback: 'AI Providers',\n },\n {\n value: 'plan-core',\n labelKey: 'vibecontrols.plugins.category.planCore',\n labelFallback: 'Plan Core',\n },\n {\n value: 'plan-provider',\n labelKey: 'vibecontrols.plugins.category.planProvider',\n labelFallback: 'Plan Providers',\n },\n {\n value: 'session-core',\n labelKey: 'vibecontrols.plugins.category.sessionCore',\n labelFallback: 'Session Core',\n },\n {\n value: 'session-provider',\n labelKey: 'vibecontrols.plugins.category.sessionProvider',\n labelFallback: 'Session Providers',\n },\n { value: 'tunnel', labelKey: 'vibecontrols.plugins.category.tunnel', labelFallback: 'Tunnel' },\n { value: 'vcs', labelKey: 'vibecontrols.plugins.category.vcs', labelFallback: 'VCS' },\n {\n value: 'api-playground',\n labelKey: 'vibecontrols.plugins.category.apiPlayground',\n labelFallback: 'API Playground',\n },\n {\n value: 'gitops-core',\n labelKey: 'vibecontrols.plugins.category.gitopsCore',\n labelFallback: 'GitOps Core',\n },\n {\n value: 'gitops-provider',\n labelKey: 'vibecontrols.plugins.category.gitopsProvider',\n labelFallback: 'GitOps Providers',\n },\n {\n value: 'security-core',\n labelKey: 'vibecontrols.plugins.category.securityCore',\n labelFallback: 'Security Core',\n },\n {\n value: 'security-provider',\n labelKey: 'vibecontrols.plugins.category.securityProvider',\n labelFallback: 'Security Providers',\n },\n { value: 'tool', labelKey: 'vibecontrols.plugins.category.tool', labelFallback: 'Tools' },\n];\n\nfunction derivePluginType(category: PluginMeta['category']): PluginType {\n if (category.endsWith('-core')) return 'Meta';\n if (category.endsWith('-provider')) return 'Provider';\n return 'Plugin';\n}\n\nfunction typeBadgeClasses(type: PluginType): string {\n switch (type) {\n case 'Meta':\n return 'bg-accent-purple-subtle text-accent-purple border-accent-purple/30';\n case 'Provider':\n return 'bg-status-info-bg text-status-info-text border-status-info-border';\n default:\n return 'bg-bg-sunken text-text-secondary border-border-default';\n }\n}\n\ninterface CopyButtonProps {\n text: string;\n ariaLabel: string;\n}\n\nfunction CopyButton({ text, ariaLabel }: CopyButtonProps) {\n const [copied, setCopied] = useState(false);\n const handleCopy = useCallback(async () => {\n try {\n if (typeof navigator !== 'undefined' && navigator.clipboard) {\n await navigator.clipboard.writeText(text);\n }\n setCopied(true);\n window.setTimeout(() => setCopied(false), 1500);\n } catch {\n setCopied(false);\n }\n }, [text]);\n\n return (\n <button\n type=\"button\"\n onClick={handleCopy}\n aria-label={ariaLabel}\n className=\"inline-flex items-center justify-center size-7 rounded border border-border-default bg-bg-surface text-text-secondary hover:text-text-primary hover:bg-bg-elevated transition-colors flex-shrink-0\"\n >\n {copied ? <Check className=\"size-3.5\" /> : <Copy className=\"size-3.5\" />}\n </button>\n );\n}\n\ninterface PluginCardProps {\n plugin: PluginMeta;\n}\n\nfunction PluginCard({ plugin }: PluginCardProps) {\n const tr = useTr();\n const type = derivePluginType(plugin.category);\n const installCommand = `npm install ${plugin.packageName}`;\n const usageSnippet = `import plugin from '${plugin.packageName}';`;\n\n const [showUsage, setShowUsage] = useState(false);\n const [showCommands, setShowCommands] = useState(false);\n\n return (\n <Card className=\"flex flex-col h-full p-0 bg-bg-surface border-border-default\">\n <div className=\"flex flex-col gap-3 p-4\">\n <div className=\"flex items-start justify-between gap-3\">\n <div className=\"min-w-0 flex-1\">\n <h3 className=\"text-base font-semibold text-text-primary truncate\">\n {plugin.displayName}\n </h3>\n <p className=\"text-xs text-text-secondary font-mono truncate mt-1\">\n {plugin.packageName}\n </p>\n </div>\n <span\n className={`inline-flex items-center gap-1 px-2 py-0.5 text-[10px] uppercase tracking-wide font-semibold rounded-full border ${typeBadgeClasses(type)} flex-shrink-0`}\n >\n {type}\n </span>\n </div>\n\n <p className=\"text-sm text-text-secondary leading-snug\">{plugin.description}</p>\n\n <div className=\"mt-1 space-y-1.5\">\n <p className=\"text-[11px] font-medium uppercase tracking-wide text-text-secondary\">\n {tr('vibecontrols.plugins.install', 'Install')}\n </p>\n <div className=\"flex items-center gap-2 rounded-md border border-border-default bg-bg-sunken px-2.5 py-1.5\">\n <code className=\"flex-1 text-xs font-mono text-text-primary truncate\">\n {installCommand}\n </code>\n <CopyButton\n text={installCommand}\n ariaLabel={tr(\n 'vibecontrols.plugins.copyInstall',\n 'Copy install command for {{name}}',\n {\n name: plugin.displayName,\n }\n )}\n />\n </div>\n </div>\n\n <div className=\"space-y-2\">\n <button\n type=\"button\"\n onClick={() => setShowUsage((value) => !value)}\n className=\"w-full flex items-center justify-between gap-2 text-left text-xs font-medium text-text-secondary hover:text-text-primary transition-colors\"\n aria-expanded={showUsage}\n >\n <span>{tr('vibecontrols.plugins.usage', 'Usage')}</span>\n <ChevronDown\n className={`size-3.5 transition-transform ${showUsage ? 'rotate-180' : ''}`}\n />\n </button>\n {showUsage && (\n <div className=\"flex items-start gap-2 rounded-md border border-border-default bg-bg-sunken px-2.5 py-2\">\n <code className=\"flex-1 text-xs font-mono text-text-primary whitespace-pre-wrap break-all\">\n {usageSnippet}\n </code>\n <CopyButton\n text={usageSnippet}\n ariaLabel={tr('vibecontrols.plugins.copyUsage', 'Copy usage snippet for {{name}}', {\n name: plugin.displayName,\n })}\n />\n </div>\n )}\n </div>\n\n <div className=\"space-y-2\">\n <button\n type=\"button\"\n onClick={() => setShowCommands((value) => !value)}\n className=\"w-full flex items-center justify-between gap-2 text-left text-xs font-medium text-text-secondary hover:text-text-primary transition-colors\"\n aria-expanded={showCommands}\n >\n <span>{tr('vibecontrols.plugins.commands', 'Command reference')}</span>\n <ChevronDown\n className={`size-3.5 transition-transform ${showCommands ? 'rotate-180' : ''}`}\n />\n </button>\n {showCommands && (\n <div className=\"rounded-md border border-border-default bg-bg-sunken px-2.5 py-2\">\n <p className=\"text-xs text-text-secondary\">\n {tr(\n 'vibecontrols.plugins.commandsHint',\n 'See the agent admin sub-panel or the plugin README for the documented command list.'\n )}\n </p>\n </div>\n )}\n </div>\n </div>\n </Card>\n );\n}\n\nexport function PluginsCatalogPage() {\n const tr = useTr();\n const [search, setSearch] = useState('');\n const [activeCategory, setActiveCategory] = useState<string>('all');\n\n const pluginList = useMemo(() => {\n return Object.values(PLUGIN_CATALOG).sort((a, b) => a.displayName.localeCompare(b.displayName));\n }, []);\n\n const filteredPlugins = useMemo(() => {\n const term = search.trim().toLowerCase();\n return pluginList.filter((plugin) => {\n if (activeCategory !== 'all' && plugin.category !== activeCategory) return false;\n if (!term) return true;\n const haystack =\n `${plugin.displayName} ${plugin.description} ${plugin.packageName}`.toLowerCase();\n return haystack.includes(term);\n });\n }, [pluginList, search, activeCategory]);\n\n return (\n <PageContainer padded={false}>\n <MotionPage className=\"flex flex-col h-full\">\n {/* Sticky header with search + filter chips */}\n <div className=\"sticky top-0 z-10 border-b border-border-subtle bg-bg-surface px-6 lg:px-8 pt-5 pb-4 space-y-3\">\n <div className=\"flex items-start gap-3\">\n <div className=\"flex size-9 flex-shrink-0 items-center justify-center rounded-lg bg-[var(--color-accent-soft)]\">\n <Plug className=\"size-5 text-action-primary-bg\" />\n </div>\n <div className=\"min-w-0 flex-1\">\n <h1 className=\"text-2xl font-semibold tracking-tight text-text-primary\">\n {tr('vibecontrols.plugins.title', 'Plugins')}\n </h1>\n <p className=\"text-sm text-text-secondary mt-0.5\">\n {tr(\n 'vibecontrols.plugins.description',\n 'Browse and install plugins to extend your VibeControls agents.'\n )}\n </p>\n </div>\n </div>\n\n <div className=\"relative\">\n <Search className=\"absolute left-3 top-1/2 -translate-y-1/2 size-4 text-text-secondary\" />\n <input\n type=\"search\"\n value={search}\n onChange={(event) => setSearch(event.target.value)}\n placeholder={tr(\n 'vibecontrols.plugins.searchPlaceholder',\n 'Search by name, description, or package…'\n )}\n aria-label={tr('vibecontrols.plugins.searchLabel', 'Search plugins')}\n className=\"w-full pl-10 pr-10 py-2 text-sm bg-bg-sunken border border-border-default rounded-md text-text-primary placeholder:text-text-placeholder focus:outline-none focus:ring-2 focus:ring-action-primary-bg focus:border-transparent transition-all\"\n />\n {search && (\n <button\n type=\"button\"\n onClick={() => setSearch('')}\n aria-label={tr('vibecontrols.plugins.clearSearch', 'Clear search')}\n className=\"absolute right-3 top-1/2 -translate-y-1/2 p-0.5 rounded hover:bg-bg-elevated transition-colors\"\n >\n <X className=\"size-4 text-text-secondary\" />\n </button>\n )}\n </div>\n\n <div\n className=\"flex flex-wrap gap-2\"\n role=\"tablist\"\n aria-label={tr('vibecontrols.plugins.categories', 'Plugin categories')}\n >\n {CATEGORY_OPTIONS.map((option) => {\n const isActive = activeCategory === option.value;\n return (\n <button\n key={option.value}\n type=\"button\"\n role=\"tab\"\n aria-selected={isActive}\n onClick={() => setActiveCategory(option.value)}\n className={`px-3 py-1 text-xs rounded-full border transition-colors ${\n isActive\n ? 'bg-action-primary-bg text-action-primary-text border-action-primary-bg'\n : 'bg-bg-surface text-text-secondary border-border-default hover:bg-bg-sunken hover:text-text-primary'\n }`}\n >\n {tr(option.labelKey, option.labelFallback)}\n </button>\n );\n })}\n </div>\n </div>\n\n {/* Grid body */}\n <div className=\"flex-1 px-4 sm:px-6 py-4\">\n {filteredPlugins.length === 0 ? (\n <div className=\"flex flex-col items-center justify-center text-center py-16 text-text-secondary\">\n <Package className=\"size-12 mb-3 opacity-50\" />\n <p className=\"text-sm font-medium text-text-primary\">\n {tr('vibecontrols.plugins.empty.title', 'No matching plugins')}\n </p>\n <p className=\"text-xs mt-1\">\n {tr(\n 'vibecontrols.plugins.empty.description',\n 'Try a different search term or category.'\n )}\n </p>\n </div>\n ) : (\n <div className=\"grid gap-3 grid-cols-1 sm:grid-cols-2 xl:grid-cols-3\">\n {filteredPlugins.map((plugin) => (\n <PluginCard key={plugin.packageName} plugin={plugin} />\n ))}\n </div>\n )}\n </div>\n </MotionPage>\n </PageContainer>\n );\n}\n"],"mappings":"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"}
@@ -138,7 +138,7 @@ function S() {
138
138
  /* @__PURE__ */ _("button", {
139
139
  type: "button",
140
140
  onClick: Q,
141
- className: "mt-2 px-3 py-1.5 text-sm rounded-lg border border-border-default hover:bg-bg-secondary transition-colors",
141
+ className: "mt-2 px-3 py-1.5 text-sm rounded-lg border border-border-default hover:bg-bg-sunken transition-colors",
142
142
  children: C("common.back", "Back")
143
143
  })
144
144
  ]
@@ -167,7 +167,7 @@ function S() {
167
167
  /* @__PURE__ */ _("button", {
168
168
  type: "button",
169
169
  onClick: Q,
170
- className: "mt-2 px-3 py-1.5 text-sm rounded-lg border border-border-default hover:bg-bg-secondary transition-colors",
170
+ className: "mt-2 px-3 py-1.5 text-sm rounded-lg border border-border-default hover:bg-bg-sunken transition-colors",
171
171
  children: C("common.back", "Back")
172
172
  })
173
173
  ]
@@ -182,7 +182,7 @@ function S() {
182
182
  /* @__PURE__ */ _("button", {
183
183
  type: "button",
184
184
  onClick: Q,
185
- className: "p-1.5 rounded hover:bg-bg-secondary transition-colors w-fit",
185
+ className: "p-1.5 rounded hover:bg-bg-sunken transition-colors w-fit",
186
186
  "aria-label": C("common.back", "Back"),
187
187
  children: /* @__PURE__ */ _(ee, { size: 18 })
188
188
  }),
@@ -209,7 +209,7 @@ function S() {
209
209
  type: "button",
210
210
  onClick: ue,
211
211
  disabled: le,
212
- className: "flex items-center gap-1.5 px-3 py-1.5 text-sm bg-status-success-bg/10 text-status-success-text border border-status-success-border/30 rounded-lg hover:bg-status-success-bg/20 transition-colors disabled:opacity-50",
212
+ className: "flex items-center gap-1.5 px-3 py-1.5 text-sm bg-status-success-bg-subtle text-status-success-text border border-status-success-border rounded-button hover:bg-bg-sunken transition-colors disabled:opacity-50",
213
213
  children: [/* @__PURE__ */ _(ne, { size: 14 }), C("scheduledJob.detail.executeNow", "Execute Now")]
214
214
  })
215
215
  })
@@ -217,7 +217,7 @@ function S() {
217
217
  }),
218
218
  L && /* @__PURE__ */ v("div", {
219
219
  role: "alert",
220
- className: "flex items-start gap-2 rounded-lg border border-status-error-border/30 bg-status-error-bg/10 px-3 py-2 text-sm text-status-error-text",
220
+ className: "flex items-start gap-2 rounded-lg border border-status-error-border bg-status-error-bg-subtle px-3 py-2 text-sm text-status-error-text",
221
221
  children: [
222
222
  /* @__PURE__ */ _(f, {
223
223
  size: 16,
@@ -230,7 +230,7 @@ function S() {
230
230
  /* @__PURE__ */ _("button", {
231
231
  type: "button",
232
232
  onClick: () => R(null),
233
- className: "p-0.5 rounded hover:bg-status-error-bg/20 transition-colors",
233
+ className: "p-0.5 rounded hover:bg-bg-sunken transition-colors",
234
234
  "aria-label": C("common.dismiss", "Dismiss"),
235
235
  children: /* @__PURE__ */ _(m, { size: 14 })
236
236
  })
@@ -391,7 +391,7 @@ function S() {
391
391
  M && /* @__PURE__ */ _("button", {
392
392
  type: "button",
393
393
  onClick: () => de(e),
394
- className: "p-1 rounded hover:bg-bg-secondary transition-colors flex-shrink-0",
394
+ className: "p-1 rounded hover:bg-bg-sunken transition-colors flex-shrink-0",
395
395
  "aria-label": C("scheduledJob.detail.viewLogs", "View logs"),
396
396
  title: C("scheduledJob.detail.viewLogs", "View logs"),
397
397
  children: /* @__PURE__ */ _(te, { size: 14 })
@@ -1 +1 @@
1
- {"version":3,"file":"ScheduledJobDetailPage.js","names":[],"sources":["../../src/pages/ScheduledJobDetailPage.tsx"],"sourcesContent":["import { useCallback, useEffect, useRef, useState } from 'react';\nimport { useParams, useNavigate, useSearchParams } from 'react-router-dom';\nimport { useTenantContext } from '@/hooks/useTenantContext';\nimport { useVibeNavigate } from '@/providers/VibeControlsProvider';\nimport { usePermissions } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { ArrowLeft, Play, Loader2, Eye, AlertCircle, X } from 'lucide-react';\nimport { MotionPage, MotionList, MotionItem } from '@burdenoff/fe-libs/shared/motion';\nimport { LogsDrawer } from '../components/vibecalendar/LogsDrawer';\nimport { STATUS_COLORS } from '../components/vibecalendar/types';\nimport type { VibeCalendarExecution } from '../components/vibecalendar/types';\nimport { VIBE_PERMISSIONS } from '../constants/permissions';\nimport {\n useExecuteScheduledJobNowMutation,\n useGetScheduledJobDetailQuery,\n} from '@/generated/wspace-operations';\n\n// INTERNAL_API jobs persist the worker envelope as\n// `{ statusCode, duration, responseData: { executeAction: { output,\n// exitCode, status, errorMessage } } }`. `adaptExecution` unwraps\n// that into the flat shape the LogsDrawer consumes.\ntype JobExecutionShape = {\n id: string;\n status?: string | null;\n startedAt?: string | null;\n completedAt?: string | null;\n output?: unknown;\n errorMessage?: string | null;\n statusCode?: number | null;\n};\nfunction adaptExecution(\n ex: JobExecutionShape,\n canReadExecutionLogs: boolean\n): VibeCalendarExecution {\n let stdout: string | null = null;\n let exitCode: number | null = ex.statusCode ?? null;\n // Nested action errors are the only failure signal on agent-exit\n // failures (top-level `errorMessage` only fires for scheduler-side\n // dispatch failures), so capture both and let the chain below\n // prefer the top-level when present.\n let nestedActionError: string | null = null;\n if (typeof ex.output === 'string') {\n stdout = ex.output;\n } else if (ex.output && typeof ex.output === 'object') {\n const envelope = ex.output as Record<string, unknown>;\n const action = (envelope.responseData as Record<string, unknown> | undefined)?.executeAction as\n | Record<string, unknown>\n | undefined;\n if (action && typeof action.output === 'string') {\n stdout = action.output;\n }\n if (action && typeof action.exitCode === 'number') {\n exitCode = action.exitCode;\n }\n if (action && typeof action.errorMessage === 'string' && action.errorMessage.length > 0) {\n nestedActionError = action.errorMessage;\n }\n if (stdout === null) {\n stdout = JSON.stringify(envelope, null, 2);\n }\n }\n // `??` only falls back for null/undefined; an empty top-level\n // string would otherwise mask a populated nested error.\n const topErrorTrimmed =\n typeof ex.errorMessage === 'string' && ex.errorMessage.trim().length > 0\n ? ex.errorMessage\n : null;\n return {\n id: ex.id,\n taskId: '',\n status: ex.status ?? 'UNKNOWN',\n // Suppress raw envelope fields for callers without `log:read`.\n output: canReadExecutionLogs ? stdout : null,\n exitCode,\n webhookStatusCode: null,\n startedAt: ex.startedAt ?? null,\n completedAt: ex.completedAt ?? null,\n error: canReadExecutionLogs ? (topErrorTrimmed ?? nestedActionError ?? null) : null,\n };\n}\n\n// In-product detail view for a wspace-scheduler-svc ScheduledJob.\n// Lives next to VibeTaskDetailPage but reads/executes through the\n// scheduler subgraph (different id space, different RBAC resource).\nexport function ScheduledJobDetailPage() {\n const { t } = useI18n();\n const tr = useCallback(\n (key: string, fallback: string): string => {\n const v = t(key);\n return v === key ? fallback : v;\n },\n [t]\n );\n const { jobId } = useParams<{ jobId: string }>();\n // Two navigators: `navigate(-1)` only goes through react-router's\n // browser-history API (no basePath rewriting needed), but the\n // absolute-path fallback has to thread the MFE basePath via\n // `useVibeNavigate` so production mounts under `/vibecontrols`\n // land on the right calendar route instead of a host 404.\n const navigate = useNavigate();\n const vibeNavigate = useVibeNavigate();\n const [searchParams] = useSearchParams();\n // Support deep-link flow: carries `?org=…&tenant=…` URL\n // overrides into the Apollo auth-link headers for this page's\n // query + mutation, so they reach the gateway even when the\n // Apollo client's storage refs point at a different tenant.\n const tenantContext = useTenantContext();\n\n // Gateway enforces `schedule:read` on `scheduledJob(id)` and\n // `schedule:execute` on `executeJobNow` — gate the UI on the\n // matching strings so users aren't UI-locked out of server actions\n // they hold or shown buttons that the server will deny.\n const { hasPermission, isLoading: permissionsLoading, permissions } = usePermissions();\n const canReadExecutionLogs = hasPermission(VIBE_PERMISSIONS.LOG_READ);\n const canExecute = hasPermission(VIBE_PERMISSIONS.SCHEDULE_EXECUTE);\n // Skip the network query for users without `schedule:read` so the\n // raw envelope never reaches the browser. The dev/legacy fallback\n // mirrors VibeTaskDetailPage's denial check (no permission strings\n // declared = no enforcement).\n const canFetchScheduledJob =\n !permissionsLoading &&\n (!permissions ||\n (permissions.permissionStrings?.length ?? 0) === 0 ||\n hasPermission(VIBE_PERMISSIONS.SCHEDULE_READ));\n\n const [showLogsDrawer, setShowLogsDrawer] = useState(false);\n const [selectedExecution, setSelectedExecution] = useState<VibeCalendarExecution | null>(null);\n // Inline-banner state — the MFE has no toast provider, so Execute\n // Now failures surface here.\n const [executionError, setExecutionError] = useState<string | null>(null);\n // `executing` flips false the moment `executeJobNow` returns, but\n // the worker still takes 1–6 s to write the new row. Surface a\n // subtle \"waiting for execution\" hint next to the executions list\n // for the duration of the poll window so the user knows the\n // system is still working.\n const [polling, setPolling] = useState(false);\n // Set when the 12 s cap fires without seeing a new row — gives\n // the user a clear cue that the worker didn't land in the\n // expected window without claiming the run actually failed\n // (it may have completed; the row may surface on a manual refresh).\n const [pollingTimedOut, setPollingTimedOut] = useState(false);\n\n const { data, loading, error, refetch } = useGetScheduledJobDetailQuery({\n variables: { id: jobId ?? '', canReadLogs: canReadExecutionLogs },\n skip: !jobId || !canFetchScheduledJob,\n fetchPolicy: 'cache-and-network',\n context: tenantContext,\n });\n const [executeNow, { loading: executing }] = useExecuteScheduledJobNowMutation();\n\n // Latest query result mirrored into a ref so handleExecuteNow can\n // snapshot `executions.total` without taking `data` as a dep\n // (which would re-mint the callback on every cache-and-network\n // refetch and bust downstream memoization).\n const dataRef = useRef(data);\n dataRef.current = data;\n\n // Poll interval owned by a ref + cleared on unmount AND on\n // jobId change so a stale `before` snapshot from a prior job\n // can't keep ticking on the new one's URL.\n const pollIntervalRef = useRef<ReturnType<typeof setInterval> | null>(null);\n useEffect(() => {\n return () => {\n if (pollIntervalRef.current) {\n clearInterval(pollIntervalRef.current);\n pollIntervalRef.current = null;\n }\n };\n }, [jobId]);\n\n // Mount tracking is split into its own effect (no deps) so\n // jobId-change cleanup above doesn't flip the flag false on a\n // still-mounted component. handleExecuteNow awaits a mutation\n // before arming the interval, so it has to verify it's still\n // mounted before starting one that the cleanup would otherwise\n // miss (unmount mid-await with ref=null).\n const mountedRef = useRef(true);\n useEffect(() => {\n return () => {\n mountedRef.current = false;\n };\n }, []);\n\n const job = data?.scheduledJob;\n const executions = job?.executions?.executions ?? [];\n\n // Deep-link / new-tab fallback: when there's no prior history\n // entry (user opened the URL directly), `navigate(-1)` is a no-op.\n // Land them back on the calendar list via the basePath-aware\n // shell navigator so production mounts under `/vibecontrols`\n // resolve to the right route, not a host 404. Carry the current\n // query string through so a `?org=…&tenant=…` deep-link doesn't\n // lose its tenant context and snap the calendar back to the\n // default/stored tenant.\n const handleBack = useCallback(() => {\n if (window.history.length > 1) {\n navigate(-1);\n return;\n }\n const search = searchParams.toString();\n vibeNavigate(search ? `/calendar?${search}` : '/calendar');\n }, [navigate, vibeNavigate, searchParams]);\n\n const handleExecuteNow = useCallback(async () => {\n if (!jobId) return;\n // Clear any prior failure banner before we re-attempt — if the\n // mutation phase below also fails its catch block will set a\n // fresh message.\n setExecutionError(null);\n\n // `executeJobNow` returns once queued; the worker writes the\n // new row 1–6 s later. Snapshot the total now and poll until it\n // increases or the 12 s cap fires. dataRef instead of data so\n // the callback stays referentially stable across refetches.\n const before = dataRef.current?.scheduledJob?.executions?.total ?? 0;\n\n // Mutation failure is the only error path the user must see.\n // A later refetch failure must not surface as \"Execute failed\"\n // because the job already ran server-side.\n //\n // VibeControlsProvider sets `errorPolicy: 'all'`, so RBAC /\n // GraphQL errors resolve into `result.error` (ApolloError)\n // instead of throwing. Inspect both paths or denials silently\n // kick off the poll cycle for a job that never ran.\n const fallbackMsg = tr(\n 'scheduledJob.detail.executeFailedFallback',\n 'Failed to execute job. Try again or contact support.'\n );\n try {\n const result = await executeNow({\n variables: { id: jobId, canReadLogs: canReadExecutionLogs },\n context: tenantContext,\n });\n if (result.error) {\n console.error('executeJobNow returned an Apollo error:', result.error);\n setExecutionError(result.error.message || fallbackMsg);\n return;\n }\n } catch (err) {\n console.error('Failed to execute job:', err);\n const msg = err instanceof Error && err.message ? err.message : fallbackMsg;\n setExecutionError(msg);\n return;\n }\n\n // Bail before any state writes if the component unmounted\n // mid-await — without this we'd arm a new setInterval that the\n // effect cleanup already missed (cleanup ran with ref=null).\n if (!mountedRef.current) return;\n\n try {\n await refetch();\n } catch (err) {\n console.error('Post-execute refetch failed (mutation already succeeded):', err);\n }\n\n if (!mountedRef.current) return;\n\n // Latest click owns the polling window — drop any prior cycle.\n if (pollIntervalRef.current) {\n clearInterval(pollIntervalRef.current);\n pollIntervalRef.current = null;\n }\n setPollingTimedOut(false);\n setPolling(true);\n let attempts = 0;\n // Locals (not the shared ref) own this cycle's bookkeeping so a\n // stale promise from a prior cycle can't cancel this interval\n // or its in-flight flag. `inFlight` skips ticks that arrive\n // before the previous refetch has resolved — a slow gateway\n // (cold start, GC pause) shouldn't pile up concurrent requests\n // against the same Apollo cache key.\n let inFlight = false;\n const intervalId = setInterval(() => {\n if (inFlight) return;\n if (attempts >= 12) {\n if (pollIntervalRef.current === intervalId) {\n clearInterval(intervalId);\n pollIntervalRef.current = null;\n if (mountedRef.current) {\n setPolling(false);\n setPollingTimedOut(true);\n }\n }\n return;\n }\n attempts += 1;\n inFlight = true;\n void refetch()\n .then((r) => {\n const now = r.data?.scheduledJob?.executions?.total ?? before;\n if (now > before) {\n if (pollIntervalRef.current === intervalId) {\n clearInterval(intervalId);\n pollIntervalRef.current = null;\n if (mountedRef.current) setPolling(false);\n }\n }\n })\n .catch(() => {\n // Cap is enforced on the next tick.\n })\n .finally(() => {\n inFlight = false;\n });\n }, 1000);\n pollIntervalRef.current = intervalId;\n }, [jobId, executeNow, refetch, canReadExecutionLogs, tenantContext, tr]);\n\n const handleViewExecution = useCallback(\n (ex: JobExecutionShape) => {\n setSelectedExecution(adaptExecution(ex, canReadExecutionLogs));\n setShowLogsDrawer(true);\n },\n [canReadExecutionLogs]\n );\n\n // Wait for the permissions provider to settle so we don't flash\n // an access-denied screen for users who actually hold the\n // permission (race: query resolves before permissions do).\n if (permissionsLoading) {\n return (\n <MotionPage className=\"flex items-center justify-center h-full p-6\">\n <p className=\"text-sm text-text-secondary\">\n {tr('scheduledJob.detail.loadingPermissions', 'Loading permissions…')}\n </p>\n </MotionPage>\n );\n }\n // Deep-link / bookmark deny — the calendar route gate only\n // protects the page mount, not this specific resource read.\n if (\n permissions &&\n (permissions.permissionStrings?.length ?? 0) > 0 &&\n !hasPermission(VIBE_PERMISSIONS.SCHEDULE_READ)\n ) {\n return (\n <MotionPage className=\"flex flex-col items-center justify-center h-full p-6 gap-3\">\n <h2 className=\"text-base font-semibold text-text-primary\">\n {tr('scheduledJob.detail.accessDenied', 'Access Denied')}\n </h2>\n <p className=\"text-sm text-text-secondary text-center max-w-md\">\n {tr(\n 'scheduledJob.detail.accessDeniedHint',\n 'You do not have permission to view scheduled-job details. Contact your workspace owner if you believe you should.'\n )}\n </p>\n <button\n type=\"button\"\n onClick={handleBack}\n className=\"mt-2 px-3 py-1.5 text-sm rounded-lg border border-border-default hover:bg-bg-secondary transition-colors\"\n >\n {tr('common.back', 'Back')}\n </button>\n </MotionPage>\n );\n }\n\n // ----- loading / error / not-found states -----\n if (loading && !job) {\n return (\n <MotionPage className=\"flex items-center justify-center h-full p-6\">\n <Loader2 size={20} className=\"animate-spin text-text-tertiary\" />\n <p className=\"ml-2 text-sm text-text-secondary\">\n {tr('scheduledJob.detail.loading', 'Loading job…')}\n </p>\n </MotionPage>\n );\n }\n // `cache-and-network` keeps `job` populated from cache while a\n // background refetch is in flight; a transient network error\n // during that refetch sets `error` without nulling `data`, so\n // dropping to the not-found state on `error` alone would discard\n // a perfectly usable cached view. Only treat the page as\n // not-found when there's actually no data to render.\n if (!job && !loading) {\n return (\n <MotionPage className=\"flex flex-col items-center justify-center h-full p-6 gap-3\">\n <p className=\"text-base font-semibold text-text-primary\">\n {tr('scheduledJob.detail.notFound', 'Job not found')}\n </p>\n <p className=\"text-sm text-text-secondary text-center max-w-md\">\n {tr(\n 'scheduledJob.detail.notFoundHint',\n 'It may have been deleted, archived, or moved to a workspace you no longer have access to.'\n )}\n </p>\n <button\n type=\"button\"\n onClick={handleBack}\n className=\"mt-2 px-3 py-1.5 text-sm rounded-lg border border-border-default hover:bg-bg-secondary transition-colors\"\n >\n {tr('common.back', 'Back')}\n </button>\n </MotionPage>\n );\n }\n\n const statusColor = STATUS_COLORS[job.status] || STATUS_COLORS.PENDING;\n const fmtTime = (iso?: string | null) =>\n iso ? new Date(iso).toLocaleString() : tr('common.notAvailable', 'Not available');\n\n return (\n <MotionPage className=\"flex flex-col h-full p-4 gap-6\">\n {/* Header */}\n <div className=\"flex flex-col gap-3 sm:flex-row sm:items-center\">\n <button\n type=\"button\"\n onClick={handleBack}\n className=\"p-1.5 rounded hover:bg-bg-secondary transition-colors w-fit\"\n aria-label={tr('common.back', 'Back')}\n >\n <ArrowLeft size={18} />\n </button>\n <div className=\"flex-1 min-w-0\">\n <div className=\"flex items-center gap-2 flex-wrap\">\n <h1 className=\"text-xl font-bold text-text-primary truncate\">\n {job.name || tr('scheduledJob.detail.untitled', 'Untitled job')}\n </h1>\n <span\n className={`px-2 py-0.5 text-xs font-medium rounded border ${statusColor}`}\n aria-label={`Status ${job.status}`}\n >\n {job.status}\n </span>\n </div>\n {job.description && (\n <p className=\"text-sm text-text-secondary mt-1 line-clamp-2\">{job.description}</p>\n )}\n </div>\n <div className=\"flex w-full flex-wrap items-center gap-2 sm:w-auto sm:justify-end\">\n {canExecute && (\n <button\n type=\"button\"\n onClick={handleExecuteNow}\n disabled={executing}\n className=\"flex items-center gap-1.5 px-3 py-1.5 text-sm bg-status-success-bg/10 text-status-success-text border border-status-success-border/30 rounded-lg hover:bg-status-success-bg/20 transition-colors disabled:opacity-50\"\n >\n <Play size={14} />\n {tr('scheduledJob.detail.executeNow', 'Execute Now')}\n </button>\n )}\n </div>\n </div>\n\n {executionError && (\n <div\n role=\"alert\"\n className=\"flex items-start gap-2 rounded-lg border border-status-error-border/30 bg-status-error-bg/10 px-3 py-2 text-sm text-status-error-text\"\n >\n <AlertCircle size={16} className=\"mt-0.5 flex-shrink-0\" />\n <p className=\"flex-1 break-words\">{executionError}</p>\n <button\n type=\"button\"\n onClick={() => setExecutionError(null)}\n className=\"p-0.5 rounded hover:bg-status-error-bg/20 transition-colors\"\n aria-label={tr('common.dismiss', 'Dismiss')}\n >\n <X size={14} />\n </button>\n </div>\n )}\n\n {/* Info Cards */}\n <MotionList stagger={0.04} className=\"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3\">\n <MotionItem className=\"bg-bg-surface border border-border-default rounded-lg p-3\">\n <p className=\"text-xs text-text-tertiary\">\n {tr('scheduledJob.detail.scheduleType', 'Schedule Type')}\n </p>\n <p className=\"text-sm font-medium text-text-primary mt-0.5\">{job.scheduleType}</p>\n {job.cronExpression && (\n <p className=\"text-xs text-text-secondary font-mono mt-1 truncate\">\n {job.cronExpression}\n </p>\n )}\n </MotionItem>\n <MotionItem className=\"bg-bg-surface border border-border-default rounded-lg p-3\">\n <p className=\"text-xs text-text-tertiary\">\n {tr('scheduledJob.detail.timezone', 'Timezone')}\n </p>\n <p className=\"text-sm font-medium text-text-primary mt-0.5\">{job.timezone || 'UTC'}</p>\n </MotionItem>\n <MotionItem className=\"bg-bg-surface border border-border-default rounded-lg p-3\">\n <p className=\"text-xs text-text-tertiary\">\n {tr('scheduledJob.detail.nextExecution', 'Next Execution')}\n </p>\n <p className=\"text-sm font-medium text-text-primary mt-0.5\">\n {fmtTime(job.nextExecutionAt)}\n </p>\n </MotionItem>\n <MotionItem className=\"bg-bg-surface border border-border-default rounded-lg p-3\">\n <p className=\"text-xs text-text-tertiary\">\n {tr('scheduledJob.detail.lastExecution', 'Last Execution')}\n </p>\n <p className=\"text-sm font-medium text-text-primary mt-0.5\">\n {fmtTime(job.lastExecutionAt)}\n </p>\n </MotionItem>\n <MotionItem className=\"bg-bg-surface border border-border-default rounded-lg p-3\">\n <p className=\"text-xs text-text-tertiary\">\n {tr('scheduledJob.detail.executor', 'Executor')}\n </p>\n <p className=\"text-sm font-medium text-text-primary mt-0.5\">\n {job.executorType || tr('common.notAvailable', 'Not available')}\n </p>\n {job.actionId && (\n <p className=\"text-xs text-text-secondary font-mono mt-1 truncate\">\n {tr('scheduledJob.detail.action', 'Action')}: {job.actionId}\n </p>\n )}\n </MotionItem>\n <MotionItem className=\"bg-bg-surface border border-border-default rounded-lg p-3\">\n <p className=\"text-xs text-text-tertiary\">\n {tr('scheduledJob.detail.priority', 'Priority')}\n </p>\n <p className=\"text-sm font-medium text-text-primary mt-0.5\">{job.priority}</p>\n </MotionItem>\n </MotionList>\n\n {/* Executions List */}\n <div className=\"flex flex-col gap-2 flex-1 min-h-0\">\n <div className=\"flex items-center justify-between flex-wrap gap-2\">\n <div className=\"flex items-center gap-2\">\n <h2 className=\"text-sm font-semibold text-text-primary\">\n {tr('scheduledJob.detail.executions', 'Executions')}\n </h2>\n {polling && (\n <span className=\"flex items-center gap-1 text-xs text-text-tertiary\">\n <Loader2 size={12} className=\"animate-spin\" />\n {tr('scheduledJob.detail.waitingForExecution', 'Waiting for execution…')}\n </span>\n )}\n {!polling && pollingTimedOut && (\n <span className=\"text-xs text-text-tertiary\">\n {tr(\n 'scheduledJob.detail.pollingTimedOut',\n \"Didn't see a new execution yet — refresh to check again.\"\n )}\n </span>\n )}\n </div>\n <p className=\"text-xs text-text-tertiary\">\n {tr('scheduledJob.detail.executionsCount', 'Total')}: {job.executions?.total ?? 0}\n </p>\n </div>\n {executions.length === 0 ? (\n <div className=\"flex items-center justify-center py-12 border border-border-default border-dashed rounded-lg\">\n <p className=\"text-sm text-text-secondary\">\n {tr('scheduledJob.detail.noExecutions', 'No executions yet')}\n </p>\n </div>\n ) : (\n <MotionList\n stagger={0.02}\n className=\"flex flex-col gap-2 overflow-auto pr-1 max-h-[60vh]\"\n >\n {executions.map((ex: JobExecutionShape) => {\n // `??` keeps null/undefined out of the badge text.\n const exStatus = ex.status ?? 'UNKNOWN';\n const exColor = STATUS_COLORS[exStatus] || STATUS_COLORS.PENDING;\n return (\n <MotionItem\n key={ex.id}\n className=\"flex items-center gap-3 bg-bg-surface border border-border-default rounded-lg p-3\"\n >\n <span\n className={`px-2 py-0.5 text-xs font-medium rounded border ${exColor} flex-shrink-0`}\n >\n {exStatus}\n </span>\n <div className=\"flex-1 min-w-0\">\n <p className=\"text-sm text-text-primary truncate\">{fmtTime(ex.startedAt)}</p>\n {ex.completedAt && (\n <p className=\"text-xs text-text-tertiary truncate\">\n {tr('scheduledJob.detail.completedAt', 'Completed')}:{' '}\n {fmtTime(ex.completedAt)}\n </p>\n )}\n </div>\n {ex.statusCode !== null && ex.statusCode !== undefined && (\n <span className=\"text-xs font-mono text-text-secondary flex-shrink-0\">\n {ex.statusCode}\n </span>\n )}\n {canReadExecutionLogs && (\n <button\n type=\"button\"\n onClick={() => handleViewExecution(ex)}\n className=\"p-1 rounded hover:bg-bg-secondary transition-colors flex-shrink-0\"\n aria-label={tr('scheduledJob.detail.viewLogs', 'View logs')}\n title={tr('scheduledJob.detail.viewLogs', 'View logs')}\n >\n <Eye size={14} />\n </button>\n )}\n </MotionItem>\n );\n })}\n </MotionList>\n )}\n </div>\n\n {/* Per-execution Logs Drawer (reused) */}\n <LogsDrawer\n isOpen={showLogsDrawer}\n onClose={() => setShowLogsDrawer(false)}\n execution={selectedExecution}\n taskTitle={job.name || tr('scheduledJob.detail.untitled', 'Untitled job')}\n // ScheduledJob executions resolve the agent through the\n // linked Action at run time, so there's no agentId to\n // associate with the drawer view — LogsDrawer destructures\n // this as `_agentId` and never reads it.\n agentId=\"\"\n />\n </MotionPage>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"ScheduledJobDetailPage.js","names":[],"sources":["../../src/pages/ScheduledJobDetailPage.tsx"],"sourcesContent":["import { useCallback, useEffect, useRef, useState } from 'react';\nimport { useParams, useNavigate, useSearchParams } from 'react-router-dom';\nimport { useTenantContext } from '@/hooks/useTenantContext';\nimport { useVibeNavigate } from '@/providers/VibeControlsProvider';\nimport { usePermissions } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { ArrowLeft, Play, Loader2, Eye, AlertCircle, X } from 'lucide-react';\nimport { MotionPage, MotionList, MotionItem } from '@burdenoff/fe-libs/shared/motion';\nimport { LogsDrawer } from '../components/vibecalendar/LogsDrawer';\nimport { STATUS_COLORS } from '../components/vibecalendar/types';\nimport type { VibeCalendarExecution } from '../components/vibecalendar/types';\nimport { VIBE_PERMISSIONS } from '../constants/permissions';\nimport {\n useExecuteScheduledJobNowMutation,\n useGetScheduledJobDetailQuery,\n} from '@/generated/wspace-operations';\n\n// INTERNAL_API jobs persist the worker envelope as\n// `{ statusCode, duration, responseData: { executeAction: { output,\n// exitCode, status, errorMessage } } }`. `adaptExecution` unwraps\n// that into the flat shape the LogsDrawer consumes.\ntype JobExecutionShape = {\n id: string;\n status?: string | null;\n startedAt?: string | null;\n completedAt?: string | null;\n output?: unknown;\n errorMessage?: string | null;\n statusCode?: number | null;\n};\nfunction adaptExecution(\n ex: JobExecutionShape,\n canReadExecutionLogs: boolean\n): VibeCalendarExecution {\n let stdout: string | null = null;\n let exitCode: number | null = ex.statusCode ?? null;\n // Nested action errors are the only failure signal on agent-exit\n // failures (top-level `errorMessage` only fires for scheduler-side\n // dispatch failures), so capture both and let the chain below\n // prefer the top-level when present.\n let nestedActionError: string | null = null;\n if (typeof ex.output === 'string') {\n stdout = ex.output;\n } else if (ex.output && typeof ex.output === 'object') {\n const envelope = ex.output as Record<string, unknown>;\n const action = (envelope.responseData as Record<string, unknown> | undefined)?.executeAction as\n | Record<string, unknown>\n | undefined;\n if (action && typeof action.output === 'string') {\n stdout = action.output;\n }\n if (action && typeof action.exitCode === 'number') {\n exitCode = action.exitCode;\n }\n if (action && typeof action.errorMessage === 'string' && action.errorMessage.length > 0) {\n nestedActionError = action.errorMessage;\n }\n if (stdout === null) {\n stdout = JSON.stringify(envelope, null, 2);\n }\n }\n // `??` only falls back for null/undefined; an empty top-level\n // string would otherwise mask a populated nested error.\n const topErrorTrimmed =\n typeof ex.errorMessage === 'string' && ex.errorMessage.trim().length > 0\n ? ex.errorMessage\n : null;\n return {\n id: ex.id,\n taskId: '',\n status: ex.status ?? 'UNKNOWN',\n // Suppress raw envelope fields for callers without `log:read`.\n output: canReadExecutionLogs ? stdout : null,\n exitCode,\n webhookStatusCode: null,\n startedAt: ex.startedAt ?? null,\n completedAt: ex.completedAt ?? null,\n error: canReadExecutionLogs ? (topErrorTrimmed ?? nestedActionError ?? null) : null,\n };\n}\n\n// In-product detail view for a wspace-scheduler-svc ScheduledJob.\n// Lives next to VibeTaskDetailPage but reads/executes through the\n// scheduler subgraph (different id space, different RBAC resource).\nexport function ScheduledJobDetailPage() {\n const { t } = useI18n();\n const tr = useCallback(\n (key: string, fallback: string): string => {\n const v = t(key);\n return v === key ? fallback : v;\n },\n [t]\n );\n const { jobId } = useParams<{ jobId: string }>();\n // Two navigators: `navigate(-1)` only goes through react-router's\n // browser-history API (no basePath rewriting needed), but the\n // absolute-path fallback has to thread the MFE basePath via\n // `useVibeNavigate` so production mounts under `/vibecontrols`\n // land on the right calendar route instead of a host 404.\n const navigate = useNavigate();\n const vibeNavigate = useVibeNavigate();\n const [searchParams] = useSearchParams();\n // Support deep-link flow: carries `?org=…&tenant=…` URL\n // overrides into the Apollo auth-link headers for this page's\n // query + mutation, so they reach the gateway even when the\n // Apollo client's storage refs point at a different tenant.\n const tenantContext = useTenantContext();\n\n // Gateway enforces `schedule:read` on `scheduledJob(id)` and\n // `schedule:execute` on `executeJobNow` — gate the UI on the\n // matching strings so users aren't UI-locked out of server actions\n // they hold or shown buttons that the server will deny.\n const { hasPermission, isLoading: permissionsLoading, permissions } = usePermissions();\n const canReadExecutionLogs = hasPermission(VIBE_PERMISSIONS.LOG_READ);\n const canExecute = hasPermission(VIBE_PERMISSIONS.SCHEDULE_EXECUTE);\n // Skip the network query for users without `schedule:read` so the\n // raw envelope never reaches the browser. The dev/legacy fallback\n // mirrors VibeTaskDetailPage's denial check (no permission strings\n // declared = no enforcement).\n const canFetchScheduledJob =\n !permissionsLoading &&\n (!permissions ||\n (permissions.permissionStrings?.length ?? 0) === 0 ||\n hasPermission(VIBE_PERMISSIONS.SCHEDULE_READ));\n\n const [showLogsDrawer, setShowLogsDrawer] = useState(false);\n const [selectedExecution, setSelectedExecution] = useState<VibeCalendarExecution | null>(null);\n // Inline-banner state — the MFE has no toast provider, so Execute\n // Now failures surface here.\n const [executionError, setExecutionError] = useState<string | null>(null);\n // `executing` flips false the moment `executeJobNow` returns, but\n // the worker still takes 1–6 s to write the new row. Surface a\n // subtle \"waiting for execution\" hint next to the executions list\n // for the duration of the poll window so the user knows the\n // system is still working.\n const [polling, setPolling] = useState(false);\n // Set when the 12 s cap fires without seeing a new row — gives\n // the user a clear cue that the worker didn't land in the\n // expected window without claiming the run actually failed\n // (it may have completed; the row may surface on a manual refresh).\n const [pollingTimedOut, setPollingTimedOut] = useState(false);\n\n const { data, loading, error, refetch } = useGetScheduledJobDetailQuery({\n variables: { id: jobId ?? '', canReadLogs: canReadExecutionLogs },\n skip: !jobId || !canFetchScheduledJob,\n fetchPolicy: 'cache-and-network',\n context: tenantContext,\n });\n const [executeNow, { loading: executing }] = useExecuteScheduledJobNowMutation();\n\n // Latest query result mirrored into a ref so handleExecuteNow can\n // snapshot `executions.total` without taking `data` as a dep\n // (which would re-mint the callback on every cache-and-network\n // refetch and bust downstream memoization).\n const dataRef = useRef(data);\n dataRef.current = data;\n\n // Poll interval owned by a ref + cleared on unmount AND on\n // jobId change so a stale `before` snapshot from a prior job\n // can't keep ticking on the new one's URL.\n const pollIntervalRef = useRef<ReturnType<typeof setInterval> | null>(null);\n useEffect(() => {\n return () => {\n if (pollIntervalRef.current) {\n clearInterval(pollIntervalRef.current);\n pollIntervalRef.current = null;\n }\n };\n }, [jobId]);\n\n // Mount tracking is split into its own effect (no deps) so\n // jobId-change cleanup above doesn't flip the flag false on a\n // still-mounted component. handleExecuteNow awaits a mutation\n // before arming the interval, so it has to verify it's still\n // mounted before starting one that the cleanup would otherwise\n // miss (unmount mid-await with ref=null).\n const mountedRef = useRef(true);\n useEffect(() => {\n return () => {\n mountedRef.current = false;\n };\n }, []);\n\n const job = data?.scheduledJob;\n const executions = job?.executions?.executions ?? [];\n\n // Deep-link / new-tab fallback: when there's no prior history\n // entry (user opened the URL directly), `navigate(-1)` is a no-op.\n // Land them back on the calendar list via the basePath-aware\n // shell navigator so production mounts under `/vibecontrols`\n // resolve to the right route, not a host 404. Carry the current\n // query string through so a `?org=…&tenant=…` deep-link doesn't\n // lose its tenant context and snap the calendar back to the\n // default/stored tenant.\n const handleBack = useCallback(() => {\n if (window.history.length > 1) {\n navigate(-1);\n return;\n }\n const search = searchParams.toString();\n vibeNavigate(search ? `/calendar?${search}` : '/calendar');\n }, [navigate, vibeNavigate, searchParams]);\n\n const handleExecuteNow = useCallback(async () => {\n if (!jobId) return;\n // Clear any prior failure banner before we re-attempt — if the\n // mutation phase below also fails its catch block will set a\n // fresh message.\n setExecutionError(null);\n\n // `executeJobNow` returns once queued; the worker writes the\n // new row 1–6 s later. Snapshot the total now and poll until it\n // increases or the 12 s cap fires. dataRef instead of data so\n // the callback stays referentially stable across refetches.\n const before = dataRef.current?.scheduledJob?.executions?.total ?? 0;\n\n // Mutation failure is the only error path the user must see.\n // A later refetch failure must not surface as \"Execute failed\"\n // because the job already ran server-side.\n //\n // VibeControlsProvider sets `errorPolicy: 'all'`, so RBAC /\n // GraphQL errors resolve into `result.error` (ApolloError)\n // instead of throwing. Inspect both paths or denials silently\n // kick off the poll cycle for a job that never ran.\n const fallbackMsg = tr(\n 'scheduledJob.detail.executeFailedFallback',\n 'Failed to execute job. Try again or contact support.'\n );\n try {\n const result = await executeNow({\n variables: { id: jobId, canReadLogs: canReadExecutionLogs },\n context: tenantContext,\n });\n if (result.error) {\n console.error('executeJobNow returned an Apollo error:', result.error);\n setExecutionError(result.error.message || fallbackMsg);\n return;\n }\n } catch (err) {\n console.error('Failed to execute job:', err);\n const msg = err instanceof Error && err.message ? err.message : fallbackMsg;\n setExecutionError(msg);\n return;\n }\n\n // Bail before any state writes if the component unmounted\n // mid-await — without this we'd arm a new setInterval that the\n // effect cleanup already missed (cleanup ran with ref=null).\n if (!mountedRef.current) return;\n\n try {\n await refetch();\n } catch (err) {\n console.error('Post-execute refetch failed (mutation already succeeded):', err);\n }\n\n if (!mountedRef.current) return;\n\n // Latest click owns the polling window — drop any prior cycle.\n if (pollIntervalRef.current) {\n clearInterval(pollIntervalRef.current);\n pollIntervalRef.current = null;\n }\n setPollingTimedOut(false);\n setPolling(true);\n let attempts = 0;\n // Locals (not the shared ref) own this cycle's bookkeeping so a\n // stale promise from a prior cycle can't cancel this interval\n // or its in-flight flag. `inFlight` skips ticks that arrive\n // before the previous refetch has resolved — a slow gateway\n // (cold start, GC pause) shouldn't pile up concurrent requests\n // against the same Apollo cache key.\n let inFlight = false;\n const intervalId = setInterval(() => {\n if (inFlight) return;\n if (attempts >= 12) {\n if (pollIntervalRef.current === intervalId) {\n clearInterval(intervalId);\n pollIntervalRef.current = null;\n if (mountedRef.current) {\n setPolling(false);\n setPollingTimedOut(true);\n }\n }\n return;\n }\n attempts += 1;\n inFlight = true;\n void refetch()\n .then((r) => {\n const now = r.data?.scheduledJob?.executions?.total ?? before;\n if (now > before) {\n if (pollIntervalRef.current === intervalId) {\n clearInterval(intervalId);\n pollIntervalRef.current = null;\n if (mountedRef.current) setPolling(false);\n }\n }\n })\n .catch(() => {\n // Cap is enforced on the next tick.\n })\n .finally(() => {\n inFlight = false;\n });\n }, 1000);\n pollIntervalRef.current = intervalId;\n }, [jobId, executeNow, refetch, canReadExecutionLogs, tenantContext, tr]);\n\n const handleViewExecution = useCallback(\n (ex: JobExecutionShape) => {\n setSelectedExecution(adaptExecution(ex, canReadExecutionLogs));\n setShowLogsDrawer(true);\n },\n [canReadExecutionLogs]\n );\n\n // Wait for the permissions provider to settle so we don't flash\n // an access-denied screen for users who actually hold the\n // permission (race: query resolves before permissions do).\n if (permissionsLoading) {\n return (\n <MotionPage className=\"flex items-center justify-center h-full p-6\">\n <p className=\"text-sm text-text-secondary\">\n {tr('scheduledJob.detail.loadingPermissions', 'Loading permissions…')}\n </p>\n </MotionPage>\n );\n }\n // Deep-link / bookmark deny — the calendar route gate only\n // protects the page mount, not this specific resource read.\n if (\n permissions &&\n (permissions.permissionStrings?.length ?? 0) > 0 &&\n !hasPermission(VIBE_PERMISSIONS.SCHEDULE_READ)\n ) {\n return (\n <MotionPage className=\"flex flex-col items-center justify-center h-full p-6 gap-3\">\n <h2 className=\"text-base font-semibold text-text-primary\">\n {tr('scheduledJob.detail.accessDenied', 'Access Denied')}\n </h2>\n <p className=\"text-sm text-text-secondary text-center max-w-md\">\n {tr(\n 'scheduledJob.detail.accessDeniedHint',\n 'You do not have permission to view scheduled-job details. Contact your workspace owner if you believe you should.'\n )}\n </p>\n <button\n type=\"button\"\n onClick={handleBack}\n className=\"mt-2 px-3 py-1.5 text-sm rounded-lg border border-border-default hover:bg-bg-sunken transition-colors\"\n >\n {tr('common.back', 'Back')}\n </button>\n </MotionPage>\n );\n }\n\n // ----- loading / error / not-found states -----\n if (loading && !job) {\n return (\n <MotionPage className=\"flex items-center justify-center h-full p-6\">\n <Loader2 size={20} className=\"animate-spin text-text-tertiary\" />\n <p className=\"ml-2 text-sm text-text-secondary\">\n {tr('scheduledJob.detail.loading', 'Loading job…')}\n </p>\n </MotionPage>\n );\n }\n // `cache-and-network` keeps `job` populated from cache while a\n // background refetch is in flight; a transient network error\n // during that refetch sets `error` without nulling `data`, so\n // dropping to the not-found state on `error` alone would discard\n // a perfectly usable cached view. Only treat the page as\n // not-found when there's actually no data to render.\n if (!job && !loading) {\n return (\n <MotionPage className=\"flex flex-col items-center justify-center h-full p-6 gap-3\">\n <p className=\"text-base font-semibold text-text-primary\">\n {tr('scheduledJob.detail.notFound', 'Job not found')}\n </p>\n <p className=\"text-sm text-text-secondary text-center max-w-md\">\n {tr(\n 'scheduledJob.detail.notFoundHint',\n 'It may have been deleted, archived, or moved to a workspace you no longer have access to.'\n )}\n </p>\n <button\n type=\"button\"\n onClick={handleBack}\n className=\"mt-2 px-3 py-1.5 text-sm rounded-lg border border-border-default hover:bg-bg-sunken transition-colors\"\n >\n {tr('common.back', 'Back')}\n </button>\n </MotionPage>\n );\n }\n\n const statusColor = STATUS_COLORS[job.status] || STATUS_COLORS.PENDING;\n const fmtTime = (iso?: string | null) =>\n iso ? new Date(iso).toLocaleString() : tr('common.notAvailable', 'Not available');\n\n return (\n <MotionPage className=\"flex flex-col h-full p-4 gap-6\">\n {/* Header */}\n <div className=\"flex flex-col gap-3 sm:flex-row sm:items-center\">\n <button\n type=\"button\"\n onClick={handleBack}\n className=\"p-1.5 rounded hover:bg-bg-sunken transition-colors w-fit\"\n aria-label={tr('common.back', 'Back')}\n >\n <ArrowLeft size={18} />\n </button>\n <div className=\"flex-1 min-w-0\">\n <div className=\"flex items-center gap-2 flex-wrap\">\n <h1 className=\"text-xl font-bold text-text-primary truncate\">\n {job.name || tr('scheduledJob.detail.untitled', 'Untitled job')}\n </h1>\n <span\n className={`px-2 py-0.5 text-xs font-medium rounded border ${statusColor}`}\n aria-label={`Status ${job.status}`}\n >\n {job.status}\n </span>\n </div>\n {job.description && (\n <p className=\"text-sm text-text-secondary mt-1 line-clamp-2\">{job.description}</p>\n )}\n </div>\n <div className=\"flex w-full flex-wrap items-center gap-2 sm:w-auto sm:justify-end\">\n {canExecute && (\n <button\n type=\"button\"\n onClick={handleExecuteNow}\n disabled={executing}\n className=\"flex items-center gap-1.5 px-3 py-1.5 text-sm bg-status-success-bg-subtle text-status-success-text border border-status-success-border rounded-button hover:bg-bg-sunken transition-colors disabled:opacity-50\"\n >\n <Play size={14} />\n {tr('scheduledJob.detail.executeNow', 'Execute Now')}\n </button>\n )}\n </div>\n </div>\n\n {executionError && (\n <div\n role=\"alert\"\n className=\"flex items-start gap-2 rounded-lg border border-status-error-border bg-status-error-bg-subtle px-3 py-2 text-sm text-status-error-text\"\n >\n <AlertCircle size={16} className=\"mt-0.5 flex-shrink-0\" />\n <p className=\"flex-1 break-words\">{executionError}</p>\n <button\n type=\"button\"\n onClick={() => setExecutionError(null)}\n className=\"p-0.5 rounded hover:bg-bg-sunken transition-colors\"\n aria-label={tr('common.dismiss', 'Dismiss')}\n >\n <X size={14} />\n </button>\n </div>\n )}\n\n {/* Info Cards */}\n <MotionList stagger={0.04} className=\"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3\">\n <MotionItem className=\"bg-bg-surface border border-border-default rounded-lg p-3\">\n <p className=\"text-xs text-text-tertiary\">\n {tr('scheduledJob.detail.scheduleType', 'Schedule Type')}\n </p>\n <p className=\"text-sm font-medium text-text-primary mt-0.5\">{job.scheduleType}</p>\n {job.cronExpression && (\n <p className=\"text-xs text-text-secondary font-mono mt-1 truncate\">\n {job.cronExpression}\n </p>\n )}\n </MotionItem>\n <MotionItem className=\"bg-bg-surface border border-border-default rounded-lg p-3\">\n <p className=\"text-xs text-text-tertiary\">\n {tr('scheduledJob.detail.timezone', 'Timezone')}\n </p>\n <p className=\"text-sm font-medium text-text-primary mt-0.5\">{job.timezone || 'UTC'}</p>\n </MotionItem>\n <MotionItem className=\"bg-bg-surface border border-border-default rounded-lg p-3\">\n <p className=\"text-xs text-text-tertiary\">\n {tr('scheduledJob.detail.nextExecution', 'Next Execution')}\n </p>\n <p className=\"text-sm font-medium text-text-primary mt-0.5\">\n {fmtTime(job.nextExecutionAt)}\n </p>\n </MotionItem>\n <MotionItem className=\"bg-bg-surface border border-border-default rounded-lg p-3\">\n <p className=\"text-xs text-text-tertiary\">\n {tr('scheduledJob.detail.lastExecution', 'Last Execution')}\n </p>\n <p className=\"text-sm font-medium text-text-primary mt-0.5\">\n {fmtTime(job.lastExecutionAt)}\n </p>\n </MotionItem>\n <MotionItem className=\"bg-bg-surface border border-border-default rounded-lg p-3\">\n <p className=\"text-xs text-text-tertiary\">\n {tr('scheduledJob.detail.executor', 'Executor')}\n </p>\n <p className=\"text-sm font-medium text-text-primary mt-0.5\">\n {job.executorType || tr('common.notAvailable', 'Not available')}\n </p>\n {job.actionId && (\n <p className=\"text-xs text-text-secondary font-mono mt-1 truncate\">\n {tr('scheduledJob.detail.action', 'Action')}: {job.actionId}\n </p>\n )}\n </MotionItem>\n <MotionItem className=\"bg-bg-surface border border-border-default rounded-lg p-3\">\n <p className=\"text-xs text-text-tertiary\">\n {tr('scheduledJob.detail.priority', 'Priority')}\n </p>\n <p className=\"text-sm font-medium text-text-primary mt-0.5\">{job.priority}</p>\n </MotionItem>\n </MotionList>\n\n {/* Executions List */}\n <div className=\"flex flex-col gap-2 flex-1 min-h-0\">\n <div className=\"flex items-center justify-between flex-wrap gap-2\">\n <div className=\"flex items-center gap-2\">\n <h2 className=\"text-sm font-semibold text-text-primary\">\n {tr('scheduledJob.detail.executions', 'Executions')}\n </h2>\n {polling && (\n <span className=\"flex items-center gap-1 text-xs text-text-tertiary\">\n <Loader2 size={12} className=\"animate-spin\" />\n {tr('scheduledJob.detail.waitingForExecution', 'Waiting for execution…')}\n </span>\n )}\n {!polling && pollingTimedOut && (\n <span className=\"text-xs text-text-tertiary\">\n {tr(\n 'scheduledJob.detail.pollingTimedOut',\n \"Didn't see a new execution yet — refresh to check again.\"\n )}\n </span>\n )}\n </div>\n <p className=\"text-xs text-text-tertiary\">\n {tr('scheduledJob.detail.executionsCount', 'Total')}: {job.executions?.total ?? 0}\n </p>\n </div>\n {executions.length === 0 ? (\n <div className=\"flex items-center justify-center py-12 border border-border-default border-dashed rounded-lg\">\n <p className=\"text-sm text-text-secondary\">\n {tr('scheduledJob.detail.noExecutions', 'No executions yet')}\n </p>\n </div>\n ) : (\n <MotionList\n stagger={0.02}\n className=\"flex flex-col gap-2 overflow-auto pr-1 max-h-[60vh]\"\n >\n {executions.map((ex: JobExecutionShape) => {\n // `??` keeps null/undefined out of the badge text.\n const exStatus = ex.status ?? 'UNKNOWN';\n const exColor = STATUS_COLORS[exStatus] || STATUS_COLORS.PENDING;\n return (\n <MotionItem\n key={ex.id}\n className=\"flex items-center gap-3 bg-bg-surface border border-border-default rounded-lg p-3\"\n >\n <span\n className={`px-2 py-0.5 text-xs font-medium rounded border ${exColor} flex-shrink-0`}\n >\n {exStatus}\n </span>\n <div className=\"flex-1 min-w-0\">\n <p className=\"text-sm text-text-primary truncate\">{fmtTime(ex.startedAt)}</p>\n {ex.completedAt && (\n <p className=\"text-xs text-text-tertiary truncate\">\n {tr('scheduledJob.detail.completedAt', 'Completed')}:{' '}\n {fmtTime(ex.completedAt)}\n </p>\n )}\n </div>\n {ex.statusCode !== null && ex.statusCode !== undefined && (\n <span className=\"text-xs font-mono text-text-secondary flex-shrink-0\">\n {ex.statusCode}\n </span>\n )}\n {canReadExecutionLogs && (\n <button\n type=\"button\"\n onClick={() => handleViewExecution(ex)}\n className=\"p-1 rounded hover:bg-bg-sunken transition-colors flex-shrink-0\"\n aria-label={tr('scheduledJob.detail.viewLogs', 'View logs')}\n title={tr('scheduledJob.detail.viewLogs', 'View logs')}\n >\n <Eye size={14} />\n </button>\n )}\n </MotionItem>\n );\n })}\n </MotionList>\n )}\n </div>\n\n {/* Per-execution Logs Drawer (reused) */}\n <LogsDrawer\n isOpen={showLogsDrawer}\n onClose={() => setShowLogsDrawer(false)}\n execution={selectedExecution}\n taskTitle={job.name || tr('scheduledJob.detail.untitled', 'Untitled job')}\n // ScheduledJob executions resolve the agent through the\n // linked Action at run time, so there's no agentId to\n // associate with the drawer view — LogsDrawer destructures\n // this as `_agentId` and never reads it.\n agentId=\"\"\n />\n </MotionPage>\n );\n}\n"],"mappings":"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"}
@@ -263,11 +263,11 @@ function S() {
263
263
  })
264
264
  });
265
265
  if (st && (st.permissionStrings?.length ?? 0) > 0 && !at(xe.SESSION_READ)) return /* @__PURE__ */ v("div", {
266
- className: "flex items-center justify-center min-h-[400px]",
266
+ className: "flex items-center justify-center min-h-[400px] px-4",
267
267
  children: /* @__PURE__ */ y("div", {
268
- className: "text-center space-y-2",
268
+ className: "text-center space-y-2 max-w-sm",
269
269
  children: [/* @__PURE__ */ v("h2", {
270
- className: "text-lg font-semibold text-foreground",
270
+ className: "text-lg font-semibold tracking-tight text-text-primary",
271
271
  children: C("sessionDetails.accessDenied", "Access Denied")
272
272
  }), /* @__PURE__ */ v("p", {
273
273
  className: "text-sm text-text-secondary",
@@ -316,7 +316,7 @@ function S() {
316
316
  action: /* @__PURE__ */ v("button", {
317
317
  type: "button",
318
318
  onClick: Z,
319
- className: "px-4 py-2 bg-action-primary-bg text-action-primary-text rounded-md hover:bg-action-primary-bg-hover transition-colors",
319
+ className: "px-4 py-2 bg-action-primary-bg text-action-primary-text rounded-button hover:bg-action-primary-bgHover transition-all duration-200 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus",
320
320
  children: C("sessionDetails.backToSessions", "Back to Sessions")
321
321
  })
322
322
  })]
@@ -382,7 +382,7 @@ function S() {
382
382
  })] }) : /* @__PURE__ */ y("button", {
383
383
  type: "button",
384
384
  onClick: sn,
385
- className: "flex items-center gap-2 px-3 py-2 bg-status-success-bg text-status-success-text rounded-md hover:opacity-80 transition-colors",
385
+ className: "flex items-center gap-2 px-3 py-2 bg-status-success-bg-subtle text-status-success-text border border-status-success-border rounded-button hover:bg-status-success-bg-subtle/80 transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus",
386
386
  children: [/* @__PURE__ */ v(Ve, { className: "size-4" }), C("sessionDetails.runCommand", "Run Command")]
387
387
  }),
388
388
  X ? /* @__PURE__ */ y(_, { children: [
@@ -415,7 +415,7 @@ function S() {
415
415
  type: "button",
416
416
  onClick: un,
417
417
  disabled: z || !A,
418
- className: "flex items-center gap-2 px-3 py-2 bg-action-primary-bg text-action-primary-text rounded-md hover:bg-action-primary-bg-hover transition-colors disabled:opacity-50",
418
+ className: "flex items-center gap-2 px-3 py-2 bg-action-primary-bg text-action-primary-text rounded-button hover:bg-action-primary-bgHover transition-all duration-200 disabled:opacity-50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus",
419
419
  title: A ? A.tunnelUrl ? C("sessionDetails.openTerminal", "Open terminal") : C("sessionDetails.agentNoTunnelUrl", "Agent has no tunnel URL configured") : C("sessionDetails.noAgentAssigned", "No agent assigned"),
420
420
  children: [z ? /* @__PURE__ */ v(h, { className: "size-4 animate-spin" }) : /* @__PURE__ */ v(g, { className: "size-4" }), C("sessionDetails.openTerminal", "Open Terminal")]
421
421
  })
@@ -431,8 +431,8 @@ function S() {
431
431
  children: /* @__PURE__ */ y("div", {
432
432
  className: "flex items-center gap-3",
433
433
  children: [/* @__PURE__ */ v("div", {
434
- className: "size-10 rounded-lg bg-bg-sunken flex items-center justify-center",
435
- children: /* @__PURE__ */ v(g, { className: "size-5 text-text-secondary" })
434
+ className: "size-10 rounded-lg bg-[var(--color-accent-soft)] flex items-center justify-center",
435
+ children: /* @__PURE__ */ v(g, { className: "size-5 text-action-primary-bg" })
436
436
  }), /* @__PURE__ */ y("div", { children: [/* @__PURE__ */ v("p", {
437
437
  className: "text-sm text-text-secondary",
438
438
  children: C("sessionDetails.statusLabel", "Status")
@@ -447,8 +447,8 @@ function S() {
447
447
  children: /* @__PURE__ */ y("div", {
448
448
  className: "flex items-center gap-3",
449
449
  children: [/* @__PURE__ */ v("div", {
450
- className: "size-10 rounded-lg bg-bg-sunken flex items-center justify-center",
451
- children: /* @__PURE__ */ v(Me, { className: "size-5 text-text-secondary" })
450
+ className: "size-10 rounded-lg bg-[var(--color-accent-soft)] flex items-center justify-center",
451
+ children: /* @__PURE__ */ v(Me, { className: "size-5 text-action-primary-bg" })
452
452
  }), /* @__PURE__ */ y("div", { children: [/* @__PURE__ */ v("p", {
453
453
  className: "text-sm text-text-secondary",
454
454
  children: C("sessionDetails.typeLabel", "Type")
@@ -463,8 +463,8 @@ function S() {
463
463
  children: /* @__PURE__ */ y("div", {
464
464
  className: "flex items-center gap-3",
465
465
  children: [/* @__PURE__ */ v("div", {
466
- className: "size-10 rounded-lg bg-bg-sunken flex items-center justify-center",
467
- children: /* @__PURE__ */ v(Fe, { className: "size-5 text-text-secondary" })
466
+ className: "size-10 rounded-lg bg-[var(--color-accent-soft)] flex items-center justify-center",
467
+ children: /* @__PURE__ */ v(Fe, { className: "size-5 text-action-primary-bg" })
468
468
  }), /* @__PURE__ */ y("div", { children: [/* @__PURE__ */ v("p", {
469
469
  className: "text-sm text-text-secondary",
470
470
  children: C("sessionDetails.pidLabel", "PID")
@@ -479,8 +479,8 @@ function S() {
479
479
  children: /* @__PURE__ */ y("div", {
480
480
  className: "flex items-center gap-3",
481
481
  children: [/* @__PURE__ */ v("div", {
482
- className: "size-10 rounded-lg bg-bg-sunken flex items-center justify-center",
483
- children: /* @__PURE__ */ v(We, { className: "size-5 text-text-secondary" })
482
+ className: "size-10 rounded-lg bg-[var(--color-accent-soft)] flex items-center justify-center",
483
+ children: /* @__PURE__ */ v(We, { className: "size-5 text-action-primary-bg" })
484
484
  }), /* @__PURE__ */ y("div", { children: [/* @__PURE__ */ v("p", {
485
485
  className: "text-sm text-text-secondary",
486
486
  children: C("sessionDetails.agentLabel", "Agent")
@@ -518,7 +518,7 @@ function S() {
518
518
  value: gt,
519
519
  onChange: (e) => _t(e.target.value),
520
520
  placeholder: C("sessionDetails.commandPlaceholder", "bun run dev"),
521
- className: "w-full px-3 py-2 text-sm bg-bg-sunken border border-border-default rounded-md text-text-primary placeholder:text-text-placeholder focus:outline-none focus:ring-2 focus:ring-action-primary-bg font-mono"
521
+ className: "w-full px-3 py-2 text-sm bg-bg-sunken border border-border-subtle rounded-input text-text-primary placeholder:text-text-placeholder focus:outline-none focus:ring-1 focus:ring-[var(--color-focus-ring)] focus:border-border-strong font-mono transition-colors"
522
522
  })] }),
523
523
  /* @__PURE__ */ y("div", { children: [/* @__PURE__ */ v("label", {
524
524
  className: "block text-xs font-medium text-text-secondary mb-1",
@@ -529,7 +529,7 @@ function S() {
529
529
  value: vt,
530
530
  onChange: (e) => yt(e.target.value),
531
531
  placeholder: C("sessionDetails.workingDirPlaceholder", "/home/user/myproject"),
532
- className: "w-full px-3 py-2 text-sm bg-bg-sunken border border-border-default rounded-md text-text-primary placeholder:text-text-placeholder focus:outline-none focus:ring-2 focus:ring-action-primary-bg font-mono"
532
+ className: "w-full px-3 py-2 text-sm bg-bg-sunken border border-border-subtle rounded-input text-text-primary placeholder:text-text-placeholder focus:outline-none focus:ring-1 focus:ring-[var(--color-focus-ring)] focus:border-border-strong font-mono transition-colors"
533
533
  })] }),
534
534
  /* @__PURE__ */ y("div", {
535
535
  className: "flex items-center gap-2",
@@ -537,7 +537,7 @@ function S() {
537
537
  type: "button",
538
538
  onClick: Ct,
539
539
  disabled: bt,
540
- className: "flex items-center gap-1.5 px-3 py-1.5 text-sm bg-action-primary-bg text-action-primary-text rounded-md hover:bg-action-primary-bg-hover transition-colors disabled:opacity-50",
540
+ className: "flex items-center gap-1.5 px-3 py-1.5 text-sm bg-action-primary-bg text-action-primary-text rounded-button hover:bg-action-primary-bgHover transition-all duration-200 disabled:opacity-50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus",
541
541
  children: [bt ? /* @__PURE__ */ v(h, { className: "size-3.5 animate-spin" }) : /* @__PURE__ */ v(Oe, { className: "size-3.5" }), C("sessionDetails.save", "Save")]
542
542
  }), /* @__PURE__ */ y("button", {
543
543
  type: "button",
@@ -646,8 +646,8 @@ function S() {
646
646
  children: [/* @__PURE__ */ y("div", {
647
647
  className: "flex items-center gap-3",
648
648
  children: [/* @__PURE__ */ v("div", {
649
- className: "size-10 rounded-lg bg-bg-sunken flex items-center justify-center",
650
- children: /* @__PURE__ */ v(We, { className: "size-5 text-text-secondary" })
649
+ className: "size-10 rounded-lg bg-[var(--color-accent-soft)] flex items-center justify-center",
650
+ children: /* @__PURE__ */ v(We, { className: "size-5 text-action-primary-bg" })
651
651
  }), /* @__PURE__ */ y("div", { children: [
652
652
  /* @__PURE__ */ v("p", {
653
653
  className: "text-sm text-text-secondary",
@@ -830,7 +830,7 @@ function S() {
830
830
  /* @__PURE__ */ v("button", {
831
831
  type: "button",
832
832
  onClick: un,
833
- className: "mt-4 px-4 py-2 bg-bg-muted text-text-primary text-sm rounded-md hover:bg-bg-sunken transition-colors",
833
+ className: "mt-4 px-4 py-2 bg-bg-sunken text-text-primary text-sm rounded-button hover:bg-bg-elevated transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus",
834
834
  children: C("sessionDetails.retry", "Retry")
835
835
  })
836
836
  ]
@@ -869,7 +869,7 @@ function S() {
869
869
  /* @__PURE__ */ v("button", {
870
870
  type: "button",
871
871
  onClick: rn,
872
- className: "mt-4 px-4 py-2 bg-bg-muted text-text-primary text-sm rounded-md hover:bg-bg-sunken transition-colors",
872
+ className: "mt-4 px-4 py-2 bg-bg-sunken text-text-primary text-sm rounded-button hover:bg-bg-elevated transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus",
873
873
  children: C("sessionDetails.retry", "Retry")
874
874
  })
875
875
  ] }) : /* @__PURE__ */ y(_, { children: [/* @__PURE__ */ v(h, { className: "size-6 text-text-tertiary mx-auto mb-3 animate-spin" }), /* @__PURE__ */ v("p", {