@burdenoff/microfe-vibecontrols 2026.623.1 → 2026.623.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (295) 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/ActionTypeBadge.js +1 -1
  4. package/dist/components/actions/ActionTypeBadge.js.map +1 -1
  5. package/dist/components/actions/ExecutionHistory.js +1 -1
  6. package/dist/components/actions/ExecutionHistory.js.map +1 -1
  7. package/dist/components/agent-manager/AgentManagerAdminBar.js +1 -1
  8. package/dist/components/agent-manager/AgentManagerAdminBar.js.map +1 -1
  9. package/dist/components/agent-manager/AgentManagerChatPane.js +2 -2
  10. package/dist/components/agent-manager/AgentManagerChatPane.js.map +1 -1
  11. package/dist/components/agent-manager/AgentManagerInputArea.js +4 -4
  12. package/dist/components/agent-manager/AgentManagerInputArea.js.map +1 -1
  13. package/dist/components/agent-manager/AgentManagerLayout.js +1 -1
  14. package/dist/components/agent-manager/AgentManagerLayout.js.map +1 -1
  15. package/dist/components/agent-manager/AgentManagerMessageItem.js +1 -1
  16. package/dist/components/agent-manager/AgentManagerMessageItem.js.map +1 -1
  17. package/dist/components/agent-manager/AgentManagerSidebar.js +5 -5
  18. package/dist/components/agent-manager/AgentManagerSidebar.js.map +1 -1
  19. package/dist/components/agent-manager/AgentManagerTab.js +1 -1
  20. package/dist/components/agent-manager/AgentManagerTab.js.map +1 -1
  21. package/dist/components/agent-manager/AgentManagerTabBar.js +1 -1
  22. package/dist/components/agent-manager/AgentManagerTabBar.js.map +1 -1
  23. package/dist/components/agent-manager/BridleConfigPanel.js +24 -24
  24. package/dist/components/agent-manager/BridleConfigPanel.js.map +1 -1
  25. package/dist/components/agent-manager/PluginInstallModal.js +1 -1
  26. package/dist/components/agent-manager/PluginInstallModal.js.map +1 -1
  27. package/dist/components/agent-manager/ProfilesPanel.js +5 -5
  28. package/dist/components/agent-manager/ProfilesPanel.js.map +1 -1
  29. package/dist/components/agent-manager/controls/AutonomySelector.js +1 -1
  30. package/dist/components/agent-manager/controls/AutonomySelector.js.map +1 -1
  31. package/dist/components/agent-manager/controls/SdkSelector.js +1 -1
  32. package/dist/components/agent-manager/controls/SdkSelector.js.map +1 -1
  33. package/dist/components/agent-manager/controls/VoiceToggle.js +1 -1
  34. package/dist/components/agent-manager/controls/VoiceToggle.js.map +1 -1
  35. package/dist/components/agent-manager/detail/SessionConfigPanel.js +6 -6
  36. package/dist/components/agent-manager/detail/SessionConfigPanel.js.map +1 -1
  37. package/dist/components/agent-manager/detail/SessionLogsPanel.js +5 -5
  38. package/dist/components/agent-manager/detail/SessionLogsPanel.js.map +1 -1
  39. package/dist/components/agent-manager/detail/SessionToolsPanel.js +1 -1
  40. package/dist/components/agent-manager/detail/SessionToolsPanel.js.map +1 -1
  41. package/dist/components/agent-manager/dialogs/NewSessionDialog.js +2 -2
  42. package/dist/components/agent-manager/dialogs/NewSessionDialog.js.map +1 -1
  43. package/dist/components/agent-manager/dialogs/RenameSessionDialog.js +1 -1
  44. package/dist/components/agent-manager/dialogs/RenameSessionDialog.js.map +1 -1
  45. package/dist/components/agents/AgentCard.js +14 -11
  46. package/dist/components/agents/AgentCard.js.map +1 -1
  47. package/dist/components/ai/AIContextsTab.js +1 -1
  48. package/dist/components/ai/AIContextsTab.js.map +1 -1
  49. package/dist/components/ai/AIOverviewTab.js +4 -4
  50. package/dist/components/ai/AIOverviewTab.js.map +1 -1
  51. package/dist/components/ai/AIPlanPanel.js +4 -4
  52. package/dist/components/ai/AIPlanPanel.js.map +1 -1
  53. package/dist/components/ai/AIPlaygroundTab.js +1 -1
  54. package/dist/components/ai/AIPlaygroundTab.js.map +1 -1
  55. package/dist/components/ai/AIPromptsTab.js +2 -2
  56. package/dist/components/ai/AIPromptsTab.js.map +1 -1
  57. package/dist/components/ai/AIProviderCredentialsCard.js +4 -4
  58. package/dist/components/ai/AIProviderCredentialsCard.js.map +1 -1
  59. package/dist/components/ai/AIQueueTab.js +4 -4
  60. package/dist/components/ai/AIQueueTab.js.map +1 -1
  61. package/dist/components/ai/AISessionsTab.js +4 -4
  62. package/dist/components/ai/AISessionsTab.js.map +1 -1
  63. package/dist/components/ai/AITasksTab.js +1 -1
  64. package/dist/components/ai/AITasksTab.js.map +1 -1
  65. package/dist/components/ai/ClaudeCredentialsCard.js +3 -3
  66. package/dist/components/ai/ClaudeCredentialsCard.js.map +1 -1
  67. package/dist/components/ai/PluginHarnessPicker.js +3 -3
  68. package/dist/components/ai/PluginHarnessPicker.js.map +1 -1
  69. package/dist/components/analytics/AnalyticsBreakdownChart.js +8 -8
  70. package/dist/components/analytics/AnalyticsBreakdownChart.js.map +1 -1
  71. package/dist/components/analytics/AnalyticsTimeSeriesChart.js +5 -5
  72. package/dist/components/analytics/AnalyticsTimeSeriesChart.js.map +1 -1
  73. package/dist/components/audit/AuditDeleteDialog.js +2 -2
  74. package/dist/components/audit/AuditDeleteDialog.js.map +1 -1
  75. package/dist/components/audit/AuditExportButton.js +5 -5
  76. package/dist/components/audit/AuditExportButton.js.map +1 -1
  77. package/dist/components/audit/AuditLogDetail.js +9 -9
  78. package/dist/components/audit/AuditLogDetail.js.map +1 -1
  79. package/dist/components/audit/AuditLogFilters.js +1 -1
  80. package/dist/components/audit/AuditLogFilters.js.map +1 -1
  81. package/dist/components/audit/AuditLogStats.js +1 -1
  82. package/dist/components/audit/AuditLogStats.js.map +1 -1
  83. package/dist/components/audit/AuditLogTable.js +28 -28
  84. package/dist/components/audit/AuditLogTable.js.map +1 -1
  85. package/dist/components/catalog/CatalogCard.js +3 -3
  86. package/dist/components/catalog/CatalogCard.js.map +1 -1
  87. package/dist/components/catalog/CatalogForm.js +1 -1
  88. package/dist/components/catalog/CatalogForm.js.map +1 -1
  89. package/dist/components/common/ActionAgentTargetNotice.js +6 -6
  90. package/dist/components/common/ActionAgentTargetNotice.js.map +1 -1
  91. package/dist/components/common/CommandEditor.js +1 -1
  92. package/dist/components/common/CommandEditor.js.map +1 -1
  93. package/dist/components/docs/DocsPageTree.js +1 -1
  94. package/dist/components/docs/DocsPageTree.js.map +1 -1
  95. package/dist/components/docs/DocsPreviewPanel.js +1 -1
  96. package/dist/components/docs/DocsPreviewPanel.js.map +1 -1
  97. package/dist/components/docs/DocsRevisionHistory.js +4 -4
  98. package/dist/components/docs/DocsRevisionHistory.js.map +1 -1
  99. package/dist/components/docs/DocsSettingsPanel.js +7 -7
  100. package/dist/components/docs/DocsSettingsPanel.js.map +1 -1
  101. package/dist/components/docs/DocsTabPanel.js +1 -1
  102. package/dist/components/docs/DocsTabPanel.js.map +1 -1
  103. package/dist/components/graph/ConnectionPermissionsDialog.js +2 -2
  104. package/dist/components/graph/ConnectionPermissionsDialog.js.map +1 -1
  105. package/dist/components/graph/MeshDeployWizard.js +3 -3
  106. package/dist/components/graph/MeshDeployWizard.js.map +1 -1
  107. package/dist/components/graph/MeshRequestPanel.js +2 -2
  108. package/dist/components/graph/MeshRequestPanel.js.map +1 -1
  109. package/dist/components/idp-components/ComponentCard.js +5 -5
  110. package/dist/components/idp-components/ComponentCard.js.map +1 -1
  111. package/dist/components/idp-components/ComponentForm.js +1 -1
  112. package/dist/components/idp-components/ComponentForm.js.map +1 -1
  113. package/dist/components/idp-components/DependencyGraphTab.js +7 -7
  114. package/dist/components/idp-components/DependencyGraphTab.js.map +1 -1
  115. package/dist/components/logs/LogViewer.js +14 -14
  116. package/dist/components/logs/LogViewer.js.map +1 -1
  117. package/dist/components/notes/NoteCard.js +1 -1
  118. package/dist/components/notes/NoteCard.js.map +1 -1
  119. package/dist/components/notes/NoteEditor.js +2 -2
  120. package/dist/components/notes/NoteEditor.js.map +1 -1
  121. package/dist/components/notes/NoteList.js +1 -1
  122. package/dist/components/notes/NoteList.js.map +1 -1
  123. package/dist/components/notes/NotesTabPanel.js +1 -1
  124. package/dist/components/notes/NotesTabPanel.js.map +1 -1
  125. package/dist/components/scorecard/ScorecardEmptyState.js +1 -1
  126. package/dist/components/scorecard/ScorecardEmptyState.js.map +1 -1
  127. package/dist/components/scorecard/ScorecardPanel.js +3 -3
  128. package/dist/components/scorecard/ScorecardPanel.js.map +1 -1
  129. package/dist/components/security/SecurityScanList.js +11 -11
  130. package/dist/components/security/SecurityScanList.js.map +1 -1
  131. package/dist/components/sessions/SessionCard.js +26 -19
  132. package/dist/components/sessions/SessionCard.js.map +1 -1
  133. package/dist/components/sessions/SessionStatusBadge.js +7 -7
  134. package/dist/components/sessions/SessionStatusBadge.js.map +1 -1
  135. package/dist/components/shared/AgentSelector.js +4 -4
  136. package/dist/components/shared/AgentSelector.js.map +1 -1
  137. package/dist/components/shared/AgentSetupBanner.js +4 -4
  138. package/dist/components/shared/AgentSetupBanner.js.map +1 -1
  139. package/dist/components/shared/Card.js +4 -4
  140. package/dist/components/shared/Card.js.map +1 -1
  141. package/dist/components/shared/ErrorState.js +15 -9
  142. package/dist/components/shared/ErrorState.js.map +1 -1
  143. package/dist/components/shared/FilterDropdown.js +2 -2
  144. package/dist/components/shared/FilterDropdown.js.map +1 -1
  145. package/dist/components/shared/PageSkeleton.js +6 -6
  146. package/dist/components/shared/PageSkeleton.js.map +1 -1
  147. package/dist/components/shared/SearchInput.js +1 -1
  148. package/dist/components/shared/SearchInput.js.map +1 -1
  149. package/dist/components/shared/StatCard.js +7 -7
  150. package/dist/components/shared/StatCard.js.map +1 -1
  151. package/dist/components/templates/PublishTemplateForm.js +2 -2
  152. package/dist/components/templates/PublishTemplateForm.js.map +1 -1
  153. package/dist/components/templates/TemplateCard.js +5 -5
  154. package/dist/components/templates/TemplateCard.js.map +1 -1
  155. package/dist/components/templates/TemplateWizard.js +2 -2
  156. package/dist/components/templates/TemplateWizard.js.map +1 -1
  157. package/dist/components/tunnels/DomainVerificationBadge.js +4 -4
  158. package/dist/components/tunnels/DomainVerificationBadge.js.map +1 -1
  159. package/dist/components/tunnels/ProviderBadge.js +1 -1
  160. package/dist/components/tunnels/ProviderBadge.js.map +1 -1
  161. package/dist/components/tunnels/TunnelCard.js +16 -16
  162. package/dist/components/tunnels/TunnelCard.js.map +1 -1
  163. package/dist/components/tunnels/TunnelCreateWizard.js +1 -1
  164. package/dist/components/tunnels/TunnelCreateWizard.js.map +1 -1
  165. package/dist/components/tunnels/TunnelPublicAccessPanel.js +1 -1
  166. package/dist/components/tunnels/TunnelPublicAccessPanel.js.map +1 -1
  167. package/dist/components/tunnels/TunnelStatusBadge.js +4 -4
  168. package/dist/components/tunnels/TunnelStatusBadge.js.map +1 -1
  169. package/dist/components/vibecalendar/LogsDrawer.js +2 -2
  170. package/dist/components/vibecalendar/LogsDrawer.js.map +1 -1
  171. package/dist/components/vibecalendar/ScheduleTaskModal.js +5 -5
  172. package/dist/components/vibecalendar/ScheduleTaskModal.js.map +1 -1
  173. package/dist/components/vibecalendar/VibeCalAgendaView.js +2 -2
  174. package/dist/components/vibecalendar/VibeCalAgendaView.js.map +1 -1
  175. package/dist/components/vibecalendar/VibeCalDayView.js +3 -3
  176. package/dist/components/vibecalendar/VibeCalDayView.js.map +1 -1
  177. package/dist/components/vibecalendar/VibeCalMonthView.js +1 -1
  178. package/dist/components/vibecalendar/VibeCalMonthView.js.map +1 -1
  179. package/dist/components/vibecalendar/VibeCalWeekView.js +4 -4
  180. package/dist/components/vibecalendar/VibeCalWeekView.js.map +1 -1
  181. package/dist/components/vibecalendar/VibeCalendarHeader.js +4 -4
  182. package/dist/components/vibecalendar/VibeCalendarHeader.js.map +1 -1
  183. package/dist/components/vibecalendar/VibeTaskBlock.js +2 -2
  184. package/dist/components/vibecalendar/VibeTaskBlock.js.map +1 -1
  185. package/dist/components/vibecalendar/types.js +7 -7
  186. package/dist/components/vibecalendar/types.js.map +1 -1
  187. package/dist/components/vibedeck/VibeDeckButtonForm.js +9 -9
  188. package/dist/components/vibedeck/VibeDeckButtonForm.js.map +1 -1
  189. package/dist/components/vibedeck/VibeDeckButtonTile.js +6 -6
  190. package/dist/components/vibedeck/VibeDeckButtonTile.js.map +1 -1
  191. package/dist/components/vibedeck/VibeDeckExecutionLog.js +10 -10
  192. package/dist/components/vibedeck/VibeDeckExecutionLog.js.map +1 -1
  193. package/dist/components/vibedeck/VibeDeckForm.js +8 -8
  194. package/dist/components/vibedeck/VibeDeckForm.js.map +1 -1
  195. package/dist/components/vibedeck/VibeDeckGrid.js +2 -2
  196. package/dist/components/vibedeck/VibeDeckGrid.js.map +1 -1
  197. package/dist/components/vibedeck/VibeDeckPublicLinkPanel.js +2 -2
  198. package/dist/components/vibedeck/VibeDeckPublicLinkPanel.js.map +1 -1
  199. package/dist/components/vibedeck/VibeDeckSwitcher.js +8 -8
  200. package/dist/components/vibedeck/VibeDeckSwitcher.js.map +1 -1
  201. package/dist/components/vibes/VibeCard.js +22 -18
  202. package/dist/components/vibes/VibeCard.js.map +1 -1
  203. package/dist/components/vibes/VibeList.js +16 -12
  204. package/dist/components/vibes/VibeList.js.map +1 -1
  205. package/dist/pages/ActionsPage.js +4 -3
  206. package/dist/pages/ActionsPage.js.map +1 -1
  207. package/dist/pages/AgentDetailsPage.js +9 -9
  208. package/dist/pages/AgentDetailsPage.js.map +1 -1
  209. package/dist/pages/AgentsLandingPage.js +4 -4
  210. package/dist/pages/AgentsLandingPage.js.map +1 -1
  211. package/dist/pages/AgentsPage.js +14 -14
  212. package/dist/pages/AgentsPage.js.map +1 -1
  213. package/dist/pages/AnalyticsPage.js +8 -8
  214. package/dist/pages/AnalyticsPage.js.map +1 -1
  215. package/dist/pages/CatalogDetailPage.js +11 -11
  216. package/dist/pages/CatalogDetailPage.js.map +1 -1
  217. package/dist/pages/CatalogPage.js +12 -9
  218. package/dist/pages/CatalogPage.js.map +1 -1
  219. package/dist/pages/ComponentDetailPage.js +7 -7
  220. package/dist/pages/ComponentDetailPage.js.map +1 -1
  221. package/dist/pages/ComponentsPage.js +12 -9
  222. package/dist/pages/ComponentsPage.js.map +1 -1
  223. package/dist/pages/EnvTopologyPage.js +2 -2
  224. package/dist/pages/EnvTopologyPage.js.map +1 -1
  225. package/dist/pages/GettingStartedPage.js +12 -12
  226. package/dist/pages/GettingStartedPage.js.map +1 -1
  227. package/dist/pages/HealthPage.js +12 -12
  228. package/dist/pages/HealthPage.js.map +1 -1
  229. package/dist/pages/LogsPage.js +4 -4
  230. package/dist/pages/LogsPage.js.map +1 -1
  231. package/dist/pages/NoteDetailsPage.js +5 -5
  232. package/dist/pages/NoteDetailsPage.js.map +1 -1
  233. package/dist/pages/OverviewPage.js +76 -43
  234. package/dist/pages/OverviewPage.js.map +1 -1
  235. package/dist/pages/PluginsCatalogPage.js +7 -4
  236. package/dist/pages/PluginsCatalogPage.js.map +1 -1
  237. package/dist/pages/ScheduledJobDetailPage.js +7 -7
  238. package/dist/pages/ScheduledJobDetailPage.js.map +1 -1
  239. package/dist/pages/SessionDetailsPage.js +21 -21
  240. package/dist/pages/SessionDetailsPage.js.map +1 -1
  241. package/dist/pages/SessionsPage.js +20 -20
  242. package/dist/pages/SessionsPage.js.map +1 -1
  243. package/dist/pages/SettingsPage.js +64 -52
  244. package/dist/pages/SettingsPage.js.map +1 -1
  245. package/dist/pages/SharedSessionPage.js +21 -21
  246. package/dist/pages/SharedSessionPage.js.map +1 -1
  247. package/dist/pages/SharedVibeDeckPage.js +5 -5
  248. package/dist/pages/SharedVibeDeckPage.js.map +1 -1
  249. package/dist/pages/TargetDetailsPage.js +56 -65
  250. package/dist/pages/TargetDetailsPage.js.map +1 -1
  251. package/dist/pages/TargetsPage.js +4 -4
  252. package/dist/pages/TargetsPage.js.map +1 -1
  253. package/dist/pages/TemplateDetailPage.js +10 -10
  254. package/dist/pages/TemplateDetailPage.js.map +1 -1
  255. package/dist/pages/TemplatesPage.js +6 -5
  256. package/dist/pages/TemplatesPage.js.map +1 -1
  257. package/dist/pages/VibeAuditPage.js +3 -3
  258. package/dist/pages/VibeAuditPage.js.map +1 -1
  259. package/dist/pages/VibeCalendarPage.js +3 -3
  260. package/dist/pages/VibeCalendarPage.js.map +1 -1
  261. package/dist/pages/VibeDeckPage.js +3 -3
  262. package/dist/pages/VibeDeckPage.js.map +1 -1
  263. package/dist/pages/VibeDetailsPage.js +15 -15
  264. package/dist/pages/VibeDetailsPage.js.map +1 -1
  265. package/dist/pages/VibeTaskDetailPage.js +5 -5
  266. package/dist/pages/VibeTaskDetailPage.js.map +1 -1
  267. package/dist/pages/VibesPage.js +18 -18
  268. package/dist/pages/VibesPage.js.map +1 -1
  269. package/dist/pages/WebhooksPage.js +11 -11
  270. package/dist/pages/WebhooksPage.js.map +1 -1
  271. package/dist/pages/ai/AIBookmarksPage.js +1 -1
  272. package/dist/pages/ai/AIBookmarksPage.js.map +1 -1
  273. package/dist/pages/ai/AISessionDetailPage.js +6 -6
  274. package/dist/pages/ai/AISessionDetailPage.js.map +1 -1
  275. package/dist/pages/tunnels/AuditTab.js +1 -1
  276. package/dist/pages/tunnels/AuditTab.js.map +1 -1
  277. package/dist/pages/tunnels/DomainsTab.js +1 -1
  278. package/dist/pages/tunnels/DomainsTab.js.map +1 -1
  279. package/dist/pages/tunnels/ProvidersTab.js +1 -1
  280. package/dist/pages/tunnels/ProvidersTab.js.map +1 -1
  281. package/dist/pages/tunnels/SessionsTab.js +1 -1
  282. package/dist/pages/tunnels/SessionsTab.js.map +1 -1
  283. package/dist/pages/tunnels/TunnelDetailsPage.js +3 -3
  284. package/dist/pages/tunnels/TunnelDetailsPage.js.map +1 -1
  285. package/dist/pages/tunnels/UsageTab.js +1 -1
  286. package/dist/pages/tunnels/UsageTab.js.map +1 -1
  287. package/dist/pages/tunnels/tabs/TunnelDomainsTab.js +16 -16
  288. package/dist/pages/tunnels/tabs/TunnelDomainsTab.js.map +1 -1
  289. package/dist/pages/tunnels/tabs/TunnelOverviewTab.js +1 -1
  290. package/dist/pages/tunnels/tabs/TunnelOverviewTab.js.map +1 -1
  291. package/dist/pages/tunnels/tabs/TunnelSessionsTab.js +52 -49
  292. package/dist/pages/tunnels/tabs/TunnelSessionsTab.js.map +1 -1
  293. package/dist/pages/tunnels/tabs/TunnelSettingsTab.js +9 -9
  294. package/dist/pages/tunnels/tabs/TunnelSettingsTab.js.map +1 -1
  295. package/package.json +1 -1
@@ -196,7 +196,7 @@ var b = {
196
196
  className: "font-medium",
197
197
  children: k.name
198
198
  }), j && /* @__PURE__ */ v("p", {
199
- className: "mt-3 rounded-md border border-status-error-border bg-status-error-bg px-3 py-2 text-sm text-status-error-text",
199
+ className: "mt-3 rounded-md border border-status-error-border bg-status-error-bg-subtle px-3 py-2 text-sm text-status-error-text",
200
200
  children: j
201
201
  })]
202
202
  }),
@@ -259,7 +259,7 @@ var b = {
259
259
  className: "space-y-4 overflow-y-auto px-5 py-4",
260
260
  children: [
261
261
  F && /* @__PURE__ */ v("div", {
262
- className: "rounded-md border border-status-error-border bg-status-error-bg px-3 py-2 text-sm text-status-error-text",
262
+ className: "rounded-md border border-status-error-border bg-status-error-bg-subtle px-3 py-2 text-sm text-status-error-text",
263
263
  children: F
264
264
  }),
265
265
  /* @__PURE__ */ y("label", {
@@ -629,7 +629,7 @@ var b = {
629
629
  })]
630
630
  }),
631
631
  s && /* @__PURE__ */ v("p", {
632
- className: "mt-2 rounded-md border border-status-warning-border bg-status-warning-bg px-2 py-1.5 text-xs text-status-warning-text",
632
+ className: "mt-2 rounded-md border border-status-warning-border bg-status-warning-bg-subtle px-2 py-1.5 text-xs text-status-warning-text",
633
633
  children: e.remediation
634
634
  })
635
635
  ]
@@ -1 +1 @@
1
- {"version":3,"file":"ScorecardPanel.js","names":[],"sources":["../../../src/components/scorecard/ScorecardPanel.tsx"],"sourcesContent":["import type { FC, FormEvent, MouseEvent } from 'react';\nimport { useState, useCallback, useEffect } from 'react';\nimport { motion, AnimatePresence } from 'framer-motion';\nimport { Plus, TrendingUp, RefreshCw, Pencil, Trash2, Info, ShieldCheck } from 'lucide-react';\nimport { LoadingState, ErrorState, Card } from '@/components/shared';\nimport { EntityShareButton } from '@/components/sharing';\nimport { ScorecardTier } from '@/generated/wspace-types';\nimport { MotionList, MotionItem } from '@burdenoff/fe-libs/shared/motion';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { useActiveContext } from '@burdenoff/fe-libs/shared/providers/shell';\nimport {\n useGetScorecardQuery,\n useUpsertScorecardMutation,\n useAddScorecardMetricMutation,\n useUpdateScorecardMetricMutation,\n useRemoveScorecardMetricMutation,\n useApplyScorecardTemplateMutation,\n} from '@/generated/wspace-operations';\nimport {\n type ScorecardMetricType,\n type ScorecardMetricStatus,\n type ScorecardMetricDirection,\n METRIC_TYPE_OPTIONS,\n DIRECTION_OPTIONS,\n STATUS_OPTIONS,\n statusConfig,\n deriveStatus,\n} from './scorecardConstants';\nimport { ScorecardEmptyState } from './ScorecardEmptyState';\nimport { ScorecardsOverviewPanel } from './ScorecardsOverviewPanel';\nimport { MetricTrend } from './MetricTrend';\n\ninterface ScorecardMetric {\n id: string;\n name: string;\n description: string | null;\n type: ScorecardMetricType;\n value: number | null;\n threshold: number | null;\n unit: string | null;\n direction: ScorecardMetricDirection;\n weight: number;\n status: ScorecardMetricStatus;\n statusOverride: ScorecardMetricStatus | null;\n remediation: string | null;\n iframeUrl: string | null;\n iframeHeight: number | null;\n}\n\nexport interface ScorecardPanelProps {\n ownerType: 'CATALOG' | 'COMPONENT' | 'VIBE';\n ownerId: string;\n onAddMetric?: () => void;\n}\n\ntype StatusMode = 'AUTO' | ScorecardMetricStatus;\n\ninterface MetricFormState {\n name: string;\n description: string;\n type: ScorecardMetricType;\n value: string;\n threshold: string;\n unit: string;\n direction: ScorecardMetricDirection;\n weight: string;\n statusMode: StatusMode;\n remediation: string;\n iframeUrl: string;\n iframeHeight: string;\n}\n\nconst DEFAULT_METRIC_FORM: MetricFormState = {\n name: '',\n description: '',\n type: 'CUSTOM',\n value: '',\n threshold: '',\n unit: '',\n direction: 'HIGHER_IS_BETTER',\n weight: '1',\n statusMode: 'AUTO',\n remediation: '',\n iframeUrl: '',\n iframeHeight: '',\n};\n\nconst inputClass =\n 'w-full rounded-md border border-border-default bg-bg-surface px-3 py-2 text-sm text-text-primary outline-none focus:border-action-primary-bg';\n\nexport const ScorecardPanel: FC<ScorecardPanelProps> = ({ ownerType, ownerId, onAddMetric }) => {\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\n const { workspaceId } = useActiveContext();\n\n const [expandedMetric, setExpandedMetric] = useState<string | null>(null);\n const [isAddMetricOpen, setIsAddMetricOpen] = useState(false);\n const [editingMetricId, setEditingMetricId] = useState<string | null>(null);\n const [deletingMetric, setDeletingMetric] = useState<{ id: string; name: string } | null>(null);\n const [deleteError, setDeleteError] = useState<string | null>(null);\n const [metricForm, setMetricForm] = useState<MetricFormState>(DEFAULT_METRIC_FORM);\n const [formError, setFormError] = useState<string | null>(null);\n const [applyingTemplateId, setApplyingTemplateId] = useState<string | null>(null);\n\n const { data, loading, error, refetch } = useGetScorecardQuery({\n variables: { ownerType, ownerId },\n skip: !ownerId,\n fetchPolicy: 'cache-and-network',\n });\n\n const [upsertScorecard, { loading: upsertingScorecard }] = useUpsertScorecardMutation();\n const [addScorecardMetric, { loading: addingMetric }] = useAddScorecardMetricMutation();\n const [updateScorecardMetric, { loading: updatingMetric }] = useUpdateScorecardMetricMutation();\n const [removeScorecardMetric, { loading: removingMetric }] = useRemoveScorecardMetricMutation();\n const [applyTemplate] = useApplyScorecardTemplateMutation();\n\n const scorecard = data?.vibecontrolsScorecard ?? null;\n const isSavingMetric = upsertingScorecard || addingMetric || updatingMetric;\n const isEditing = editingMetricId !== null;\n\n const resetMetricForm = (): void => {\n setMetricForm(DEFAULT_METRIC_FORM);\n setFormError(null);\n setEditingMetricId(null);\n };\n\n const handleAddMetricClick = (): void => {\n if (onAddMetric) {\n onAddMetric();\n return;\n }\n resetMetricForm();\n setIsAddMetricOpen(true);\n };\n\n const handleEditMetricClick = (event: MouseEvent, metric: ScorecardMetric): void => {\n event.stopPropagation();\n setMetricForm({\n name: metric.name,\n description: metric.description ?? '',\n type: metric.type,\n value: metric.value !== null && metric.value !== undefined ? String(metric.value) : '',\n threshold:\n metric.threshold !== null && metric.threshold !== undefined ? String(metric.threshold) : '',\n unit: metric.unit ?? '',\n direction: metric.direction ?? 'HIGHER_IS_BETTER',\n weight: metric.weight !== null && metric.weight !== undefined ? String(metric.weight) : '1',\n statusMode: metric.statusOverride ?? 'AUTO',\n remediation: metric.remediation ?? '',\n iframeUrl: metric.iframeUrl ?? '',\n iframeHeight:\n metric.iframeHeight !== null && metric.iframeHeight !== undefined\n ? String(metric.iframeHeight)\n : '',\n });\n setFormError(null);\n setEditingMetricId(metric.id);\n setIsAddMetricOpen(true);\n };\n\n const handleRemoveMetricClick = (event: MouseEvent, metric: ScorecardMetric): void => {\n event.stopPropagation();\n setDeleteError(null);\n setDeletingMetric({ id: metric.id, name: metric.name });\n };\n\n const closeAddMetricDialog = (): void => {\n if (isSavingMetric) return;\n setIsAddMetricOpen(false);\n resetMetricForm();\n };\n\n const closeDeleteDialog = useCallback((): void => {\n if (removingMetric) return;\n setDeletingMetric(null);\n setDeleteError(null);\n }, [removingMetric]);\n\n useEffect(() => {\n if (!deletingMetric) return undefined;\n const handler = (event: KeyboardEvent): void => {\n if (event.key === 'Escape') closeDeleteDialog();\n };\n window.addEventListener('keydown', handler);\n return () => window.removeEventListener('keydown', handler);\n }, [deletingMetric, closeDeleteDialog]);\n\n const confirmRemoveMetric = async (): Promise<void> => {\n if (!deletingMetric) return;\n setDeleteError(null);\n try {\n await removeScorecardMetric({ variables: { id: deletingMetric.id } });\n await refetch();\n setDeletingMetric(null);\n } catch (caughtError) {\n setDeleteError(\n caughtError instanceof Error ? caughtError.message : 'Failed to remove metric'\n );\n }\n };\n\n const updateMetricForm = (field: keyof MetricFormState, value: string): void => {\n setMetricForm((current) => ({ ...current, [field]: value }));\n };\n\n const parseOptionalNumber = (value: string, label: string): number | undefined => {\n const trimmed = value.trim();\n if (!trimmed) return undefined;\n const parsed = Number(trimmed);\n if (!Number.isFinite(parsed)) throw new Error(`${label} must be a valid number`);\n return parsed;\n };\n\n const ensureScorecardId = async (): Promise<string> => {\n if (scorecard?.id) return scorecard.id;\n const created = await upsertScorecard({ variables: { input: { ownerType, ownerId } } });\n const id = created.data?.upsertVibecontrolsScorecard.id;\n if (!id) throw new Error(tr('scorecard.scorecardCreateFailed', 'Unable to create scorecard'));\n return id;\n };\n\n const handleApplyTemplate = async (templateId: string): Promise<void> => {\n setApplyingTemplateId(templateId);\n try {\n await applyTemplate({ variables: { templateId, ownerType, ownerId } });\n await refetch();\n } finally {\n setApplyingTemplateId(null);\n }\n };\n\n const handleAddMetricSubmit = async (event: FormEvent<HTMLFormElement>): Promise<void> => {\n event.preventDefault();\n setFormError(null);\n\n const name = metricForm.name.trim();\n if (!name) {\n setFormError(tr('scorecard.metricNameRequired', 'Metric name is required'));\n return;\n }\n\n // 'AUTO' → derive status (send no explicit status). Otherwise pin it.\n const statusOverride =\n metricForm.statusMode === 'AUTO'\n ? undefined\n : (metricForm.statusMode as ScorecardMetricStatus);\n\n try {\n const sharedInput = {\n name,\n description: metricForm.description.trim() || null,\n type: metricForm.type,\n value: parseOptionalNumber(metricForm.value, tr('scorecard.value', 'Value')) ?? null,\n threshold:\n parseOptionalNumber(metricForm.threshold, tr('scorecard.threshold', 'Threshold')) ?? null,\n unit: metricForm.unit.trim() || null,\n direction: metricForm.direction,\n weight: parseOptionalNumber(metricForm.weight, tr('scorecard.weight', 'Weight')) ?? 1,\n status: statusOverride ?? null,\n remediation: metricForm.remediation.trim() || null,\n iframeUrl: metricForm.iframeUrl.trim() || null,\n iframeHeight:\n parseOptionalNumber(\n metricForm.iframeHeight,\n tr('scorecard.iframeHeight', 'Iframe height')\n ) ?? null,\n };\n\n if (editingMetricId) {\n await updateScorecardMetric({ variables: { id: editingMetricId, input: sharedInput } });\n } else {\n const scorecardId = await ensureScorecardId();\n await addScorecardMetric({ variables: { scorecardId, input: sharedInput } });\n }\n\n await refetch();\n setIsAddMetricOpen(false);\n resetMetricForm();\n } catch (caughtError) {\n setFormError(\n caughtError instanceof Error\n ? caughtError.message\n : editingMetricId\n ? 'Failed to update metric'\n : 'Failed to add metric'\n );\n }\n };\n\n // Live status preview for the form.\n const previewStatus =\n metricForm.statusMode === 'AUTO'\n ? deriveStatus(\n metricForm.value.trim() ? Number(metricForm.value) : null,\n metricForm.threshold.trim() ? Number(metricForm.threshold) : null,\n metricForm.direction\n )\n : (metricForm.statusMode as ScorecardMetricStatus);\n\n const renderDeleteMetricDialog = () => (\n <AnimatePresence>\n {deletingMetric && (\n <motion.div\n initial={{ opacity: 0 }}\n animate={{ opacity: 1 }}\n exit={{ opacity: 0 }}\n className=\"fixed inset-0 z-50 flex items-center justify-center bg-bg-overlay p-4\"\n onClick={closeDeleteDialog}\n >\n <motion.div\n initial={{ scale: 0.96, y: 12 }}\n animate={{ scale: 1, y: 0 }}\n exit={{ scale: 0.96, y: 12 }}\n className=\"w-full max-w-md rounded-xl border border-border-default bg-bg-surface shadow-xl\"\n onClick={(event) => event.stopPropagation()}\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby=\"scorecard-delete-title\"\n >\n <div className=\"border-b border-border-default px-5 py-4\">\n <h3 id=\"scorecard-delete-title\" className=\"text-lg font-semibold text-text-primary\">\n {tr('scorecard.removeMetricTitle', 'Remove metric')}\n </h3>\n <p className=\"mt-1 text-sm text-text-secondary\">\n {tr(\n 'scorecard.removeMetricConfirm',\n 'Are you sure you want to remove this metric? This cannot be undone.'\n )}\n </p>\n </div>\n <div className=\"px-5 py-4 text-sm text-text-primary\">\n <p className=\"font-medium\">{deletingMetric.name}</p>\n {deleteError && (\n <p className=\"mt-3 rounded-md border border-status-error-border bg-status-error-bg px-3 py-2 text-sm text-status-error-text\">\n {deleteError}\n </p>\n )}\n </div>\n <div className=\"flex items-center justify-end gap-3 border-t border-border-default px-5 py-4\">\n <button\n type=\"button\"\n onClick={closeDeleteDialog}\n disabled={removingMetric}\n className=\"rounded-md border border-border-default px-4 py-2 text-sm text-text-primary hover:bg-bg-sunken disabled:cursor-not-allowed disabled:opacity-60\"\n >\n {tr('common.cancel', 'Cancel')}\n </button>\n <button\n type=\"button\"\n onClick={confirmRemoveMetric}\n disabled={removingMetric}\n className=\"rounded-md bg-status-error-bg px-4 py-2 text-sm text-status-error-text hover:opacity-90 disabled:cursor-not-allowed disabled:opacity-60\"\n >\n {removingMetric\n ? tr('scorecard.removingMetric', 'Removing...')\n : tr('scorecard.removeMetric', 'Remove metric')}\n </button>\n </div>\n </motion.div>\n </motion.div>\n )}\n </AnimatePresence>\n );\n\n const renderAddMetricDialog = () => {\n const previewConf = statusConfig[previewStatus] ?? statusConfig.UNKNOWN;\n return (\n <AnimatePresence>\n {isAddMetricOpen && (\n <motion.div\n initial={{ opacity: 0 }}\n animate={{ opacity: 1 }}\n exit={{ opacity: 0 }}\n className=\"fixed inset-0 z-50 flex items-center justify-center bg-bg-overlay p-4\"\n onClick={closeAddMetricDialog}\n >\n <motion.div\n initial={{ scale: 0.96, y: 12 }}\n animate={{ scale: 1, y: 0 }}\n exit={{ scale: 0.96, y: 12 }}\n className=\"w-full max-w-2xl rounded-xl border border-border-default bg-bg-surface shadow-xl\"\n onClick={(event) => event.stopPropagation()}\n >\n <form onSubmit={handleAddMetricSubmit} className=\"flex max-h-[85vh] flex-col\">\n <div className=\"border-b border-border-default px-5 py-4\">\n <h3 className=\"text-lg font-semibold text-text-primary\">\n {isEditing\n ? tr('scorecard.editMetricTitle', 'Edit scorecard metric')\n : tr('scorecard.addMetricTitle', 'Add scorecard metric')}\n </h3>\n <p className=\"mt-1 text-sm text-text-secondary\">\n {tr(\n 'scorecard.addMetricDescription',\n 'Define a metric. Status is derived from value vs threshold unless you pin it.'\n )}\n </p>\n </div>\n\n <div className=\"space-y-4 overflow-y-auto px-5 py-4\">\n {formError && (\n <div className=\"rounded-md border border-status-error-border bg-status-error-bg px-3 py-2 text-sm text-status-error-text\">\n {formError}\n </div>\n )}\n\n <label className=\"block space-y-1.5\">\n <span className=\"text-sm font-medium text-text-primary\">\n {tr('scorecard.metricName', 'Metric name')}\n </span>\n <input\n aria-label=\"control\"\n value={metricForm.name}\n onChange={(event) => updateMetricForm('name', event.target.value)}\n className={inputClass}\n placeholder={tr('scorecard.metricNamePlaceholder', 'Test coverage')}\n />\n </label>\n\n <label className=\"block space-y-1.5\">\n <span className=\"text-sm font-medium text-text-primary\">\n {tr('scorecard.metricDescription', 'Description')}\n </span>\n <textarea\n aria-label=\"control\"\n value={metricForm.description}\n onChange={(event) => updateMetricForm('description', event.target.value)}\n className={`min-h-16 ${inputClass}`}\n placeholder={tr(\n 'scorecard.metricDescriptionPlaceholder',\n 'Explain what this metric measures'\n )}\n />\n </label>\n\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2\">\n <label className=\"block space-y-1.5\">\n <span className=\"text-sm font-medium text-text-primary\">\n {tr('scorecard.metricType', 'Type')}\n </span>\n <select\n value={metricForm.type}\n onChange={(event) =>\n updateMetricForm('type', event.target.value as ScorecardMetricType)\n }\n className={inputClass}\n >\n {METRIC_TYPE_OPTIONS.map((option) => (\n <option key={option.value} value={option.value}>\n {option.label}\n </option>\n ))}\n </select>\n </label>\n\n <label className=\"block space-y-1.5\">\n <span className=\"text-sm font-medium text-text-primary\">\n {tr('scorecard.metricDirection', 'Direction')}\n </span>\n <select\n value={metricForm.direction}\n onChange={(event) =>\n updateMetricForm(\n 'direction',\n event.target.value as ScorecardMetricDirection\n )\n }\n className={inputClass}\n >\n {DIRECTION_OPTIONS.map((option) => (\n <option key={option.value} value={option.value}>\n {option.label}\n </option>\n ))}\n </select>\n </label>\n </div>\n\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-4\">\n <label className=\"block space-y-1.5\">\n <span className=\"text-sm font-medium text-text-primary\">\n {tr('scorecard.value', 'Value')}\n </span>\n <input\n aria-label=\"value\"\n value={metricForm.value}\n onChange={(event) => updateMetricForm('value', event.target.value)}\n inputMode=\"decimal\"\n className={inputClass}\n placeholder=\"86\"\n />\n </label>\n\n <label className=\"block space-y-1.5\">\n <span className=\"text-sm font-medium text-text-primary\">\n {tr('scorecard.threshold', 'Threshold')}\n </span>\n <input\n aria-label=\"threshold\"\n value={metricForm.threshold}\n onChange={(event) => updateMetricForm('threshold', event.target.value)}\n inputMode=\"decimal\"\n className={inputClass}\n placeholder=\"80\"\n />\n </label>\n\n <label className=\"block space-y-1.5\">\n <span className=\"text-sm font-medium text-text-primary\">\n {tr('scorecard.unit', 'Unit')}\n </span>\n <input\n aria-label=\"unit\"\n value={metricForm.unit}\n onChange={(event) => updateMetricForm('unit', event.target.value)}\n className={inputClass}\n placeholder=\"%\"\n />\n </label>\n\n <label className=\"block space-y-1.5\">\n <span className=\"text-sm font-medium text-text-primary\">\n {tr('scorecard.weight', 'Weight')}\n </span>\n <input\n aria-label=\"weight\"\n value={metricForm.weight}\n onChange={(event) => updateMetricForm('weight', event.target.value)}\n inputMode=\"decimal\"\n className={inputClass}\n placeholder=\"1\"\n />\n </label>\n </div>\n\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2\">\n <label className=\"block space-y-1.5\">\n <span className=\"text-sm font-medium text-text-primary\">\n {tr('scorecard.metricStatus', 'Status')}\n </span>\n <select\n value={metricForm.statusMode}\n onChange={(event) =>\n updateMetricForm('statusMode', event.target.value as StatusMode)\n }\n className={inputClass}\n >\n {STATUS_OPTIONS.map((option) => (\n <option key={option.value} value={option.value}>\n {option.label}\n </option>\n ))}\n </select>\n </label>\n\n <div className=\"flex items-end\">\n <div\n className={`flex items-center gap-2 rounded-md px-3 py-2 text-sm ${previewConf.bg} ${previewConf.text}`}\n >\n <previewConf.icon className=\"size-4\" />\n <span>\n {metricForm.statusMode === 'AUTO' ? 'Derived: ' : 'Pinned: '}\n {previewConf.label}\n </span>\n </div>\n </div>\n </div>\n\n <label className=\"block space-y-1.5\">\n <span className=\"text-sm font-medium text-text-primary\">\n {tr('scorecard.remediation', 'Remediation guidance')}\n </span>\n <textarea\n aria-label=\"control\"\n value={metricForm.remediation}\n onChange={(event) => updateMetricForm('remediation', event.target.value)}\n className={`min-h-16 ${inputClass}`}\n placeholder={tr(\n 'scorecard.remediationPlaceholder',\n 'What should someone do when this metric is failing?'\n )}\n />\n </label>\n\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-[1fr_10rem]\">\n <label className=\"block space-y-1.5\">\n <span className=\"text-sm font-medium text-text-primary\">\n {tr('scorecard.iframeUrl', 'Embed URL')}\n </span>\n <input\n aria-label=\"control\"\n value={metricForm.iframeUrl}\n onChange={(event) => updateMetricForm('iframeUrl', event.target.value)}\n className={inputClass}\n placeholder={tr('scorecard.iframeUrlPlaceholder', 'Optional dashboard URL')}\n />\n </label>\n\n <label className=\"block space-y-1.5\">\n <span className=\"text-sm font-medium text-text-primary\">\n {tr('scorecard.iframeHeight', 'Iframe height')}\n </span>\n <input\n aria-label=\"iframe height\"\n value={metricForm.iframeHeight}\n onChange={(event) => updateMetricForm('iframeHeight', event.target.value)}\n inputMode=\"numeric\"\n className={inputClass}\n placeholder=\"320\"\n />\n </label>\n </div>\n </div>\n\n <div className=\"flex items-center justify-end gap-3 border-t border-border-default px-5 py-4\">\n <button\n type=\"button\"\n onClick={closeAddMetricDialog}\n disabled={isSavingMetric}\n className=\"rounded-md border border-border-default px-4 py-2 text-sm text-text-primary hover:bg-bg-sunken disabled:cursor-not-allowed disabled:opacity-60\"\n >\n {tr('common.cancel', 'Cancel')}\n </button>\n <button\n type=\"submit\"\n disabled={isSavingMetric}\n className=\"rounded-md bg-action-primary-bg px-4 py-2 text-sm text-action-primary-text hover:bg-action-primary-bg-hover disabled:cursor-not-allowed disabled:opacity-60\"\n >\n {isSavingMetric\n ? tr('scorecard.savingMetric', 'Saving...')\n : isEditing\n ? tr('scorecard.updateMetric', 'Update Metric')\n : tr('scorecard.saveMetric', 'Save Metric')}\n </button>\n </div>\n </form>\n </motion.div>\n </motion.div>\n )}\n </AnimatePresence>\n );\n };\n\n if (loading && !scorecard) {\n return <LoadingState message={tr('scorecard.loading', 'Loading scorecard...')} />;\n }\n\n if (error && !scorecard) {\n return (\n <ErrorState\n title={tr('scorecard.failedLoad', 'Failed to load scorecard')}\n message={error.message}\n onRetry={() => refetch()}\n />\n );\n }\n\n if (!scorecard || scorecard.metrics.length === 0) {\n return (\n <>\n <ScorecardEmptyState\n ownerType={ownerType}\n ownerId={ownerId}\n workspaceId={workspaceId}\n applyingTemplateId={applyingTemplateId}\n onApplyTemplate={handleApplyTemplate}\n onAddManual={handleAddMetricClick}\n />\n {renderAddMetricDialog()}\n {renderDeleteMetricDialog()}\n </>\n );\n }\n\n const overallScore = scorecard.overallScore ?? 0;\n const scoreColor =\n overallScore >= 80\n ? 'text-status-success-text'\n : overallScore >= 50\n ? 'text-status-warning-text'\n : 'text-status-error-text';\n\n return (\n <div className=\"space-y-6\">\n {/* Overall Score */}\n <Card padding=\"lg\">\n <div className=\"flex items-center justify-between\">\n <div>\n <p className=\"flex items-center gap-1.5 text-sm font-medium text-text-secondary\">\n {tr('scorecard.overallScore', 'Overall Score')}\n <span title=\"Weighted average of each metric's score (0–100). Status is derived from value vs threshold.\">\n <Info className=\"size-3.5 text-text-muted\" />\n </span>\n </p>\n <p className={`text-4xl font-bold ${scoreColor}`}>\n {overallScore}\n <span className=\"text-lg text-text-muted\">/100</span>\n </p>\n </div>\n <div className=\"flex items-center gap-2\">\n {scorecard.securityTier && <TierBadge tier={scorecard.securityTier} />}\n <TrendingUp className={`size-6 ${scoreColor}`} />\n <EntityShareButton\n entity=\"scorecard\"\n resource={{\n id: scorecard.id,\n // Scorecard has no display-name field; use its id as the title.\n name: scorecard.id,\n owner: { ownerId, ownerType },\n }}\n label={tr('scorecard.share', 'Share')}\n />\n <button\n type=\"button\"\n onClick={() => refetch()}\n className=\"rounded-md p-2 text-text-secondary transition-colors hover:bg-bg-sunken\"\n title={tr('common.refresh', 'Refresh')}\n >\n <RefreshCw className=\"size-4\" />\n </button>\n <button\n type=\"button\"\n onClick={handleAddMetricClick}\n className=\"flex items-center gap-2 rounded-md bg-action-primary-bg px-3 py-2 text-sm text-action-primary-text transition-colors hover:bg-action-primary-bg-hover\"\n >\n <Plus className=\"size-4\" />\n {tr('scorecard.addMetric', 'Add Metric')}\n </button>\n </div>\n </div>\n </Card>\n\n {/* Metrics Grid */}\n <MotionList stagger={0.04} className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3\">\n {scorecard.metrics.map((metric: ScorecardMetric) => {\n const config = statusConfig[metric.status] || statusConfig.UNKNOWN;\n const StatusIcon = config.icon;\n const value = metric.value ?? 0;\n const threshold = metric.threshold ?? 100;\n const percentage =\n threshold > 0 ? Math.min(100, Math.max(0, (value / threshold) * 100)) : 0;\n const isExpanded = expandedMetric === metric.id;\n const showRemediation =\n metric.remediation && (metric.status === 'WARNING' || metric.status === 'FAILING');\n\n return (\n <MotionItem key={metric.id}>\n <Card hoverable padding=\"none\">\n <div\n role=\"button\"\n tabIndex={0}\n onClick={() => setExpandedMetric(isExpanded ? null : metric.id)}\n onKeyDown={(event) => {\n if (event.target !== event.currentTarget) return;\n if (event.key === 'Enter' || event.key === ' ') {\n event.preventDefault();\n setExpandedMetric(isExpanded ? null : metric.id);\n }\n }}\n className=\"cursor-pointer rounded-lg p-4 focus:outline-none focus:ring-2 focus:ring-action-primary-bg\"\n aria-expanded={isExpanded}\n aria-label={`${metric.name} metric details`}\n >\n <div className=\"flex items-start justify-between gap-2\">\n <div className=\"min-w-0 flex-1\">\n <p className=\"truncate text-sm font-medium text-text-primary\">\n {metric.name}\n </p>\n <p className=\"mt-0.5 flex items-center gap-1.5 text-xs text-text-muted\">\n <span>\n {metric.direction === 'LOWER_IS_BETTER'\n ? '↓ lower better'\n : '↑ higher better'}\n </span>\n {metric.weight && metric.weight !== 1 ? (\n <span>· ×{metric.weight}</span>\n ) : null}\n {metric.statusOverride ? <span>· pinned</span> : null}\n </p>\n </div>\n <div className=\"flex flex-shrink-0 items-center gap-1\">\n <button\n type=\"button\"\n onClick={(event) => handleEditMetricClick(event, metric)}\n className=\"rounded-md p-1 text-text-secondary hover:bg-bg-sunken hover:text-text-primary focus:outline-none focus:ring-2 focus:ring-action-primary-bg\"\n aria-label={tr('scorecard.editMetric', 'Edit metric')}\n title={tr('scorecard.editMetric', 'Edit metric')}\n >\n <Pencil className=\"size-4\" />\n </button>\n <button\n type=\"button\"\n onClick={(event) => handleRemoveMetricClick(event, metric)}\n className=\"rounded-md p-1 text-text-secondary hover:bg-status-error-bg hover:text-status-error-text focus:outline-none focus:ring-2 focus:ring-status-error-text\"\n aria-label={tr('scorecard.removeMetric', 'Remove metric')}\n title={tr('scorecard.removeMetric', 'Remove metric')}\n >\n <Trash2 className=\"size-4\" />\n </button>\n <div\n className={`flex size-7 items-center justify-center rounded-full ${config.bg}`}\n >\n <StatusIcon className={`size-4 ${config.text}`} />\n </div>\n </div>\n </div>\n\n {/* Value and progress */}\n <div className=\"mt-3\">\n <div className=\"flex items-baseline justify-between\">\n <span className=\"text-lg font-bold text-text-primary\">\n {value}\n {metric.unit ? ` ${metric.unit}` : ''}\n </span>\n <span className=\"text-xs text-text-muted\">\n / {threshold}\n {metric.unit ? ` ${metric.unit}` : ''}\n </span>\n </div>\n <div className=\"mt-1.5 h-1.5 w-full overflow-hidden rounded-full bg-bg-sunken\">\n <motion.div\n initial={{ width: 0 }}\n animate={{ width: `${percentage}%` }}\n transition={{ duration: 0.5, ease: 'easeOut' }}\n className={`h-full rounded-full ${config.bar}`}\n />\n </div>\n </div>\n\n {showRemediation && (\n <p className=\"mt-2 rounded-md border border-status-warning-border bg-status-warning-bg px-2 py-1.5 text-xs text-status-warning-text\">\n {metric.remediation}\n </p>\n )}\n </div>\n\n {/* Expanded: trend + optional embed */}\n <AnimatePresence>\n {isExpanded && (\n <motion.div\n initial={{ height: 0, opacity: 0 }}\n animate={{ height: 'auto', opacity: 1 }}\n exit={{ height: 0, opacity: 0 }}\n transition={{ duration: 0.2 }}\n className=\"overflow-hidden border-t border-border-default\"\n >\n <div className=\"px-4 py-3\">\n <MetricTrend metricId={metric.id} colorClass={config.text} />\n </div>\n {metric.iframeUrl && (\n <iframe\n src={metric.iframeUrl}\n className=\"w-full border-0\"\n style={{\n height: metric.iframeHeight ? `${metric.iframeHeight}px` : '200px',\n }}\n title={`${metric.name} embed`}\n sandbox=\"allow-scripts allow-same-origin\"\n />\n )}\n </motion.div>\n )}\n </AnimatePresence>\n </Card>\n </MotionItem>\n );\n })}\n </MotionList>\n\n <ScorecardsOverviewPanel ownerType={ownerType} highlightOwnerId={ownerId} />\n\n {renderAddMetricDialog()}\n {renderDeleteMetricDialog()}\n </div>\n );\n};\n\nconst TIER_STYLE: Record<ScorecardTier, { label: string; cls: string }> = {\n [ScorecardTier.Bronze]: {\n label: 'Bronze',\n cls: 'bg-status-warning-bg-subtle text-status-warning-text border border-status-warning-border',\n },\n [ScorecardTier.Silver]: {\n label: 'Silver',\n cls: 'bg-bg-sunken text-text-secondary border border-border-strong',\n },\n [ScorecardTier.Gold]: {\n label: 'Gold',\n cls: 'bg-status-success-bg-subtle text-status-success-text border border-status-success-border',\n },\n};\n\n/**\n * TierBadge — shows the scorecard's derived production-readiness tier (Bronze/Silver/Gold).\n * The tier is computed server-side from the tier-tagged metrics; this is read-only.\n */\nconst TierBadge: FC<{ tier: ScorecardTier }> = ({ tier }) => {\n const style = TIER_STYLE[tier];\n return (\n <span\n className={`inline-flex items-center gap-1 rounded-full px-2.5 py-1 text-xs font-semibold ${style.cls}`}\n title={`Security readiness tier: ${style.label}`}\n >\n <ShieldCheck className=\"size-3.5\" />\n {style.label}\n </span>\n );\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"ScorecardPanel.js","names":[],"sources":["../../../src/components/scorecard/ScorecardPanel.tsx"],"sourcesContent":["import type { FC, FormEvent, MouseEvent } from 'react';\nimport { useState, useCallback, useEffect } from 'react';\nimport { motion, AnimatePresence } from 'framer-motion';\nimport { Plus, TrendingUp, RefreshCw, Pencil, Trash2, Info, ShieldCheck } from 'lucide-react';\nimport { LoadingState, ErrorState, Card } from '@/components/shared';\nimport { EntityShareButton } from '@/components/sharing';\nimport { ScorecardTier } from '@/generated/wspace-types';\nimport { MotionList, MotionItem } from '@burdenoff/fe-libs/shared/motion';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { useActiveContext } from '@burdenoff/fe-libs/shared/providers/shell';\nimport {\n useGetScorecardQuery,\n useUpsertScorecardMutation,\n useAddScorecardMetricMutation,\n useUpdateScorecardMetricMutation,\n useRemoveScorecardMetricMutation,\n useApplyScorecardTemplateMutation,\n} from '@/generated/wspace-operations';\nimport {\n type ScorecardMetricType,\n type ScorecardMetricStatus,\n type ScorecardMetricDirection,\n METRIC_TYPE_OPTIONS,\n DIRECTION_OPTIONS,\n STATUS_OPTIONS,\n statusConfig,\n deriveStatus,\n} from './scorecardConstants';\nimport { ScorecardEmptyState } from './ScorecardEmptyState';\nimport { ScorecardsOverviewPanel } from './ScorecardsOverviewPanel';\nimport { MetricTrend } from './MetricTrend';\n\ninterface ScorecardMetric {\n id: string;\n name: string;\n description: string | null;\n type: ScorecardMetricType;\n value: number | null;\n threshold: number | null;\n unit: string | null;\n direction: ScorecardMetricDirection;\n weight: number;\n status: ScorecardMetricStatus;\n statusOverride: ScorecardMetricStatus | null;\n remediation: string | null;\n iframeUrl: string | null;\n iframeHeight: number | null;\n}\n\nexport interface ScorecardPanelProps {\n ownerType: 'CATALOG' | 'COMPONENT' | 'VIBE';\n ownerId: string;\n onAddMetric?: () => void;\n}\n\ntype StatusMode = 'AUTO' | ScorecardMetricStatus;\n\ninterface MetricFormState {\n name: string;\n description: string;\n type: ScorecardMetricType;\n value: string;\n threshold: string;\n unit: string;\n direction: ScorecardMetricDirection;\n weight: string;\n statusMode: StatusMode;\n remediation: string;\n iframeUrl: string;\n iframeHeight: string;\n}\n\nconst DEFAULT_METRIC_FORM: MetricFormState = {\n name: '',\n description: '',\n type: 'CUSTOM',\n value: '',\n threshold: '',\n unit: '',\n direction: 'HIGHER_IS_BETTER',\n weight: '1',\n statusMode: 'AUTO',\n remediation: '',\n iframeUrl: '',\n iframeHeight: '',\n};\n\nconst inputClass =\n 'w-full rounded-md border border-border-default bg-bg-surface px-3 py-2 text-sm text-text-primary outline-none focus:border-action-primary-bg';\n\nexport const ScorecardPanel: FC<ScorecardPanelProps> = ({ ownerType, ownerId, onAddMetric }) => {\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\n const { workspaceId } = useActiveContext();\n\n const [expandedMetric, setExpandedMetric] = useState<string | null>(null);\n const [isAddMetricOpen, setIsAddMetricOpen] = useState(false);\n const [editingMetricId, setEditingMetricId] = useState<string | null>(null);\n const [deletingMetric, setDeletingMetric] = useState<{ id: string; name: string } | null>(null);\n const [deleteError, setDeleteError] = useState<string | null>(null);\n const [metricForm, setMetricForm] = useState<MetricFormState>(DEFAULT_METRIC_FORM);\n const [formError, setFormError] = useState<string | null>(null);\n const [applyingTemplateId, setApplyingTemplateId] = useState<string | null>(null);\n\n const { data, loading, error, refetch } = useGetScorecardQuery({\n variables: { ownerType, ownerId },\n skip: !ownerId,\n fetchPolicy: 'cache-and-network',\n });\n\n const [upsertScorecard, { loading: upsertingScorecard }] = useUpsertScorecardMutation();\n const [addScorecardMetric, { loading: addingMetric }] = useAddScorecardMetricMutation();\n const [updateScorecardMetric, { loading: updatingMetric }] = useUpdateScorecardMetricMutation();\n const [removeScorecardMetric, { loading: removingMetric }] = useRemoveScorecardMetricMutation();\n const [applyTemplate] = useApplyScorecardTemplateMutation();\n\n const scorecard = data?.vibecontrolsScorecard ?? null;\n const isSavingMetric = upsertingScorecard || addingMetric || updatingMetric;\n const isEditing = editingMetricId !== null;\n\n const resetMetricForm = (): void => {\n setMetricForm(DEFAULT_METRIC_FORM);\n setFormError(null);\n setEditingMetricId(null);\n };\n\n const handleAddMetricClick = (): void => {\n if (onAddMetric) {\n onAddMetric();\n return;\n }\n resetMetricForm();\n setIsAddMetricOpen(true);\n };\n\n const handleEditMetricClick = (event: MouseEvent, metric: ScorecardMetric): void => {\n event.stopPropagation();\n setMetricForm({\n name: metric.name,\n description: metric.description ?? '',\n type: metric.type,\n value: metric.value !== null && metric.value !== undefined ? String(metric.value) : '',\n threshold:\n metric.threshold !== null && metric.threshold !== undefined ? String(metric.threshold) : '',\n unit: metric.unit ?? '',\n direction: metric.direction ?? 'HIGHER_IS_BETTER',\n weight: metric.weight !== null && metric.weight !== undefined ? String(metric.weight) : '1',\n statusMode: metric.statusOverride ?? 'AUTO',\n remediation: metric.remediation ?? '',\n iframeUrl: metric.iframeUrl ?? '',\n iframeHeight:\n metric.iframeHeight !== null && metric.iframeHeight !== undefined\n ? String(metric.iframeHeight)\n : '',\n });\n setFormError(null);\n setEditingMetricId(metric.id);\n setIsAddMetricOpen(true);\n };\n\n const handleRemoveMetricClick = (event: MouseEvent, metric: ScorecardMetric): void => {\n event.stopPropagation();\n setDeleteError(null);\n setDeletingMetric({ id: metric.id, name: metric.name });\n };\n\n const closeAddMetricDialog = (): void => {\n if (isSavingMetric) return;\n setIsAddMetricOpen(false);\n resetMetricForm();\n };\n\n const closeDeleteDialog = useCallback((): void => {\n if (removingMetric) return;\n setDeletingMetric(null);\n setDeleteError(null);\n }, [removingMetric]);\n\n useEffect(() => {\n if (!deletingMetric) return undefined;\n const handler = (event: KeyboardEvent): void => {\n if (event.key === 'Escape') closeDeleteDialog();\n };\n window.addEventListener('keydown', handler);\n return () => window.removeEventListener('keydown', handler);\n }, [deletingMetric, closeDeleteDialog]);\n\n const confirmRemoveMetric = async (): Promise<void> => {\n if (!deletingMetric) return;\n setDeleteError(null);\n try {\n await removeScorecardMetric({ variables: { id: deletingMetric.id } });\n await refetch();\n setDeletingMetric(null);\n } catch (caughtError) {\n setDeleteError(\n caughtError instanceof Error ? caughtError.message : 'Failed to remove metric'\n );\n }\n };\n\n const updateMetricForm = (field: keyof MetricFormState, value: string): void => {\n setMetricForm((current) => ({ ...current, [field]: value }));\n };\n\n const parseOptionalNumber = (value: string, label: string): number | undefined => {\n const trimmed = value.trim();\n if (!trimmed) return undefined;\n const parsed = Number(trimmed);\n if (!Number.isFinite(parsed)) throw new Error(`${label} must be a valid number`);\n return parsed;\n };\n\n const ensureScorecardId = async (): Promise<string> => {\n if (scorecard?.id) return scorecard.id;\n const created = await upsertScorecard({ variables: { input: { ownerType, ownerId } } });\n const id = created.data?.upsertVibecontrolsScorecard.id;\n if (!id) throw new Error(tr('scorecard.scorecardCreateFailed', 'Unable to create scorecard'));\n return id;\n };\n\n const handleApplyTemplate = async (templateId: string): Promise<void> => {\n setApplyingTemplateId(templateId);\n try {\n await applyTemplate({ variables: { templateId, ownerType, ownerId } });\n await refetch();\n } finally {\n setApplyingTemplateId(null);\n }\n };\n\n const handleAddMetricSubmit = async (event: FormEvent<HTMLFormElement>): Promise<void> => {\n event.preventDefault();\n setFormError(null);\n\n const name = metricForm.name.trim();\n if (!name) {\n setFormError(tr('scorecard.metricNameRequired', 'Metric name is required'));\n return;\n }\n\n // 'AUTO' → derive status (send no explicit status). Otherwise pin it.\n const statusOverride =\n metricForm.statusMode === 'AUTO'\n ? undefined\n : (metricForm.statusMode as ScorecardMetricStatus);\n\n try {\n const sharedInput = {\n name,\n description: metricForm.description.trim() || null,\n type: metricForm.type,\n value: parseOptionalNumber(metricForm.value, tr('scorecard.value', 'Value')) ?? null,\n threshold:\n parseOptionalNumber(metricForm.threshold, tr('scorecard.threshold', 'Threshold')) ?? null,\n unit: metricForm.unit.trim() || null,\n direction: metricForm.direction,\n weight: parseOptionalNumber(metricForm.weight, tr('scorecard.weight', 'Weight')) ?? 1,\n status: statusOverride ?? null,\n remediation: metricForm.remediation.trim() || null,\n iframeUrl: metricForm.iframeUrl.trim() || null,\n iframeHeight:\n parseOptionalNumber(\n metricForm.iframeHeight,\n tr('scorecard.iframeHeight', 'Iframe height')\n ) ?? null,\n };\n\n if (editingMetricId) {\n await updateScorecardMetric({ variables: { id: editingMetricId, input: sharedInput } });\n } else {\n const scorecardId = await ensureScorecardId();\n await addScorecardMetric({ variables: { scorecardId, input: sharedInput } });\n }\n\n await refetch();\n setIsAddMetricOpen(false);\n resetMetricForm();\n } catch (caughtError) {\n setFormError(\n caughtError instanceof Error\n ? caughtError.message\n : editingMetricId\n ? 'Failed to update metric'\n : 'Failed to add metric'\n );\n }\n };\n\n // Live status preview for the form.\n const previewStatus =\n metricForm.statusMode === 'AUTO'\n ? deriveStatus(\n metricForm.value.trim() ? Number(metricForm.value) : null,\n metricForm.threshold.trim() ? Number(metricForm.threshold) : null,\n metricForm.direction\n )\n : (metricForm.statusMode as ScorecardMetricStatus);\n\n const renderDeleteMetricDialog = () => (\n <AnimatePresence>\n {deletingMetric && (\n <motion.div\n initial={{ opacity: 0 }}\n animate={{ opacity: 1 }}\n exit={{ opacity: 0 }}\n className=\"fixed inset-0 z-50 flex items-center justify-center bg-bg-overlay p-4\"\n onClick={closeDeleteDialog}\n >\n <motion.div\n initial={{ scale: 0.96, y: 12 }}\n animate={{ scale: 1, y: 0 }}\n exit={{ scale: 0.96, y: 12 }}\n className=\"w-full max-w-md rounded-xl border border-border-default bg-bg-surface shadow-xl\"\n onClick={(event) => event.stopPropagation()}\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby=\"scorecard-delete-title\"\n >\n <div className=\"border-b border-border-default px-5 py-4\">\n <h3 id=\"scorecard-delete-title\" className=\"text-lg font-semibold text-text-primary\">\n {tr('scorecard.removeMetricTitle', 'Remove metric')}\n </h3>\n <p className=\"mt-1 text-sm text-text-secondary\">\n {tr(\n 'scorecard.removeMetricConfirm',\n 'Are you sure you want to remove this metric? This cannot be undone.'\n )}\n </p>\n </div>\n <div className=\"px-5 py-4 text-sm text-text-primary\">\n <p className=\"font-medium\">{deletingMetric.name}</p>\n {deleteError && (\n <p className=\"mt-3 rounded-md border border-status-error-border bg-status-error-bg-subtle px-3 py-2 text-sm text-status-error-text\">\n {deleteError}\n </p>\n )}\n </div>\n <div className=\"flex items-center justify-end gap-3 border-t border-border-default px-5 py-4\">\n <button\n type=\"button\"\n onClick={closeDeleteDialog}\n disabled={removingMetric}\n className=\"rounded-md border border-border-default px-4 py-2 text-sm text-text-primary hover:bg-bg-sunken disabled:cursor-not-allowed disabled:opacity-60\"\n >\n {tr('common.cancel', 'Cancel')}\n </button>\n <button\n type=\"button\"\n onClick={confirmRemoveMetric}\n disabled={removingMetric}\n className=\"rounded-md bg-status-error-bg px-4 py-2 text-sm text-status-error-text hover:opacity-90 disabled:cursor-not-allowed disabled:opacity-60\"\n >\n {removingMetric\n ? tr('scorecard.removingMetric', 'Removing...')\n : tr('scorecard.removeMetric', 'Remove metric')}\n </button>\n </div>\n </motion.div>\n </motion.div>\n )}\n </AnimatePresence>\n );\n\n const renderAddMetricDialog = () => {\n const previewConf = statusConfig[previewStatus] ?? statusConfig.UNKNOWN;\n return (\n <AnimatePresence>\n {isAddMetricOpen && (\n <motion.div\n initial={{ opacity: 0 }}\n animate={{ opacity: 1 }}\n exit={{ opacity: 0 }}\n className=\"fixed inset-0 z-50 flex items-center justify-center bg-bg-overlay p-4\"\n onClick={closeAddMetricDialog}\n >\n <motion.div\n initial={{ scale: 0.96, y: 12 }}\n animate={{ scale: 1, y: 0 }}\n exit={{ scale: 0.96, y: 12 }}\n className=\"w-full max-w-2xl rounded-xl border border-border-default bg-bg-surface shadow-xl\"\n onClick={(event) => event.stopPropagation()}\n >\n <form onSubmit={handleAddMetricSubmit} className=\"flex max-h-[85vh] flex-col\">\n <div className=\"border-b border-border-default px-5 py-4\">\n <h3 className=\"text-lg font-semibold text-text-primary\">\n {isEditing\n ? tr('scorecard.editMetricTitle', 'Edit scorecard metric')\n : tr('scorecard.addMetricTitle', 'Add scorecard metric')}\n </h3>\n <p className=\"mt-1 text-sm text-text-secondary\">\n {tr(\n 'scorecard.addMetricDescription',\n 'Define a metric. Status is derived from value vs threshold unless you pin it.'\n )}\n </p>\n </div>\n\n <div className=\"space-y-4 overflow-y-auto px-5 py-4\">\n {formError && (\n <div className=\"rounded-md border border-status-error-border bg-status-error-bg-subtle px-3 py-2 text-sm text-status-error-text\">\n {formError}\n </div>\n )}\n\n <label className=\"block space-y-1.5\">\n <span className=\"text-sm font-medium text-text-primary\">\n {tr('scorecard.metricName', 'Metric name')}\n </span>\n <input\n aria-label=\"control\"\n value={metricForm.name}\n onChange={(event) => updateMetricForm('name', event.target.value)}\n className={inputClass}\n placeholder={tr('scorecard.metricNamePlaceholder', 'Test coverage')}\n />\n </label>\n\n <label className=\"block space-y-1.5\">\n <span className=\"text-sm font-medium text-text-primary\">\n {tr('scorecard.metricDescription', 'Description')}\n </span>\n <textarea\n aria-label=\"control\"\n value={metricForm.description}\n onChange={(event) => updateMetricForm('description', event.target.value)}\n className={`min-h-16 ${inputClass}`}\n placeholder={tr(\n 'scorecard.metricDescriptionPlaceholder',\n 'Explain what this metric measures'\n )}\n />\n </label>\n\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2\">\n <label className=\"block space-y-1.5\">\n <span className=\"text-sm font-medium text-text-primary\">\n {tr('scorecard.metricType', 'Type')}\n </span>\n <select\n value={metricForm.type}\n onChange={(event) =>\n updateMetricForm('type', event.target.value as ScorecardMetricType)\n }\n className={inputClass}\n >\n {METRIC_TYPE_OPTIONS.map((option) => (\n <option key={option.value} value={option.value}>\n {option.label}\n </option>\n ))}\n </select>\n </label>\n\n <label className=\"block space-y-1.5\">\n <span className=\"text-sm font-medium text-text-primary\">\n {tr('scorecard.metricDirection', 'Direction')}\n </span>\n <select\n value={metricForm.direction}\n onChange={(event) =>\n updateMetricForm(\n 'direction',\n event.target.value as ScorecardMetricDirection\n )\n }\n className={inputClass}\n >\n {DIRECTION_OPTIONS.map((option) => (\n <option key={option.value} value={option.value}>\n {option.label}\n </option>\n ))}\n </select>\n </label>\n </div>\n\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-4\">\n <label className=\"block space-y-1.5\">\n <span className=\"text-sm font-medium text-text-primary\">\n {tr('scorecard.value', 'Value')}\n </span>\n <input\n aria-label=\"value\"\n value={metricForm.value}\n onChange={(event) => updateMetricForm('value', event.target.value)}\n inputMode=\"decimal\"\n className={inputClass}\n placeholder=\"86\"\n />\n </label>\n\n <label className=\"block space-y-1.5\">\n <span className=\"text-sm font-medium text-text-primary\">\n {tr('scorecard.threshold', 'Threshold')}\n </span>\n <input\n aria-label=\"threshold\"\n value={metricForm.threshold}\n onChange={(event) => updateMetricForm('threshold', event.target.value)}\n inputMode=\"decimal\"\n className={inputClass}\n placeholder=\"80\"\n />\n </label>\n\n <label className=\"block space-y-1.5\">\n <span className=\"text-sm font-medium text-text-primary\">\n {tr('scorecard.unit', 'Unit')}\n </span>\n <input\n aria-label=\"unit\"\n value={metricForm.unit}\n onChange={(event) => updateMetricForm('unit', event.target.value)}\n className={inputClass}\n placeholder=\"%\"\n />\n </label>\n\n <label className=\"block space-y-1.5\">\n <span className=\"text-sm font-medium text-text-primary\">\n {tr('scorecard.weight', 'Weight')}\n </span>\n <input\n aria-label=\"weight\"\n value={metricForm.weight}\n onChange={(event) => updateMetricForm('weight', event.target.value)}\n inputMode=\"decimal\"\n className={inputClass}\n placeholder=\"1\"\n />\n </label>\n </div>\n\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2\">\n <label className=\"block space-y-1.5\">\n <span className=\"text-sm font-medium text-text-primary\">\n {tr('scorecard.metricStatus', 'Status')}\n </span>\n <select\n value={metricForm.statusMode}\n onChange={(event) =>\n updateMetricForm('statusMode', event.target.value as StatusMode)\n }\n className={inputClass}\n >\n {STATUS_OPTIONS.map((option) => (\n <option key={option.value} value={option.value}>\n {option.label}\n </option>\n ))}\n </select>\n </label>\n\n <div className=\"flex items-end\">\n <div\n className={`flex items-center gap-2 rounded-md px-3 py-2 text-sm ${previewConf.bg} ${previewConf.text}`}\n >\n <previewConf.icon className=\"size-4\" />\n <span>\n {metricForm.statusMode === 'AUTO' ? 'Derived: ' : 'Pinned: '}\n {previewConf.label}\n </span>\n </div>\n </div>\n </div>\n\n <label className=\"block space-y-1.5\">\n <span className=\"text-sm font-medium text-text-primary\">\n {tr('scorecard.remediation', 'Remediation guidance')}\n </span>\n <textarea\n aria-label=\"control\"\n value={metricForm.remediation}\n onChange={(event) => updateMetricForm('remediation', event.target.value)}\n className={`min-h-16 ${inputClass}`}\n placeholder={tr(\n 'scorecard.remediationPlaceholder',\n 'What should someone do when this metric is failing?'\n )}\n />\n </label>\n\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-[1fr_10rem]\">\n <label className=\"block space-y-1.5\">\n <span className=\"text-sm font-medium text-text-primary\">\n {tr('scorecard.iframeUrl', 'Embed URL')}\n </span>\n <input\n aria-label=\"control\"\n value={metricForm.iframeUrl}\n onChange={(event) => updateMetricForm('iframeUrl', event.target.value)}\n className={inputClass}\n placeholder={tr('scorecard.iframeUrlPlaceholder', 'Optional dashboard URL')}\n />\n </label>\n\n <label className=\"block space-y-1.5\">\n <span className=\"text-sm font-medium text-text-primary\">\n {tr('scorecard.iframeHeight', 'Iframe height')}\n </span>\n <input\n aria-label=\"iframe height\"\n value={metricForm.iframeHeight}\n onChange={(event) => updateMetricForm('iframeHeight', event.target.value)}\n inputMode=\"numeric\"\n className={inputClass}\n placeholder=\"320\"\n />\n </label>\n </div>\n </div>\n\n <div className=\"flex items-center justify-end gap-3 border-t border-border-default px-5 py-4\">\n <button\n type=\"button\"\n onClick={closeAddMetricDialog}\n disabled={isSavingMetric}\n className=\"rounded-md border border-border-default px-4 py-2 text-sm text-text-primary hover:bg-bg-sunken disabled:cursor-not-allowed disabled:opacity-60\"\n >\n {tr('common.cancel', 'Cancel')}\n </button>\n <button\n type=\"submit\"\n disabled={isSavingMetric}\n className=\"rounded-md bg-action-primary-bg px-4 py-2 text-sm text-action-primary-text hover:bg-action-primary-bg-hover disabled:cursor-not-allowed disabled:opacity-60\"\n >\n {isSavingMetric\n ? tr('scorecard.savingMetric', 'Saving...')\n : isEditing\n ? tr('scorecard.updateMetric', 'Update Metric')\n : tr('scorecard.saveMetric', 'Save Metric')}\n </button>\n </div>\n </form>\n </motion.div>\n </motion.div>\n )}\n </AnimatePresence>\n );\n };\n\n if (loading && !scorecard) {\n return <LoadingState message={tr('scorecard.loading', 'Loading scorecard...')} />;\n }\n\n if (error && !scorecard) {\n return (\n <ErrorState\n title={tr('scorecard.failedLoad', 'Failed to load scorecard')}\n message={error.message}\n onRetry={() => refetch()}\n />\n );\n }\n\n if (!scorecard || scorecard.metrics.length === 0) {\n return (\n <>\n <ScorecardEmptyState\n ownerType={ownerType}\n ownerId={ownerId}\n workspaceId={workspaceId}\n applyingTemplateId={applyingTemplateId}\n onApplyTemplate={handleApplyTemplate}\n onAddManual={handleAddMetricClick}\n />\n {renderAddMetricDialog()}\n {renderDeleteMetricDialog()}\n </>\n );\n }\n\n const overallScore = scorecard.overallScore ?? 0;\n const scoreColor =\n overallScore >= 80\n ? 'text-status-success-text'\n : overallScore >= 50\n ? 'text-status-warning-text'\n : 'text-status-error-text';\n\n return (\n <div className=\"space-y-6\">\n {/* Overall Score */}\n <Card padding=\"lg\">\n <div className=\"flex items-center justify-between\">\n <div>\n <p className=\"flex items-center gap-1.5 text-sm font-medium text-text-secondary\">\n {tr('scorecard.overallScore', 'Overall Score')}\n <span title=\"Weighted average of each metric's score (0–100). Status is derived from value vs threshold.\">\n <Info className=\"size-3.5 text-text-muted\" />\n </span>\n </p>\n <p className={`text-4xl font-bold ${scoreColor}`}>\n {overallScore}\n <span className=\"text-lg text-text-muted\">/100</span>\n </p>\n </div>\n <div className=\"flex items-center gap-2\">\n {scorecard.securityTier && <TierBadge tier={scorecard.securityTier} />}\n <TrendingUp className={`size-6 ${scoreColor}`} />\n <EntityShareButton\n entity=\"scorecard\"\n resource={{\n id: scorecard.id,\n // Scorecard has no display-name field; use its id as the title.\n name: scorecard.id,\n owner: { ownerId, ownerType },\n }}\n label={tr('scorecard.share', 'Share')}\n />\n <button\n type=\"button\"\n onClick={() => refetch()}\n className=\"rounded-md p-2 text-text-secondary transition-colors hover:bg-bg-sunken\"\n title={tr('common.refresh', 'Refresh')}\n >\n <RefreshCw className=\"size-4\" />\n </button>\n <button\n type=\"button\"\n onClick={handleAddMetricClick}\n className=\"flex items-center gap-2 rounded-md bg-action-primary-bg px-3 py-2 text-sm text-action-primary-text transition-colors hover:bg-action-primary-bg-hover\"\n >\n <Plus className=\"size-4\" />\n {tr('scorecard.addMetric', 'Add Metric')}\n </button>\n </div>\n </div>\n </Card>\n\n {/* Metrics Grid */}\n <MotionList stagger={0.04} className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3\">\n {scorecard.metrics.map((metric: ScorecardMetric) => {\n const config = statusConfig[metric.status] || statusConfig.UNKNOWN;\n const StatusIcon = config.icon;\n const value = metric.value ?? 0;\n const threshold = metric.threshold ?? 100;\n const percentage =\n threshold > 0 ? Math.min(100, Math.max(0, (value / threshold) * 100)) : 0;\n const isExpanded = expandedMetric === metric.id;\n const showRemediation =\n metric.remediation && (metric.status === 'WARNING' || metric.status === 'FAILING');\n\n return (\n <MotionItem key={metric.id}>\n <Card hoverable padding=\"none\">\n <div\n role=\"button\"\n tabIndex={0}\n onClick={() => setExpandedMetric(isExpanded ? null : metric.id)}\n onKeyDown={(event) => {\n if (event.target !== event.currentTarget) return;\n if (event.key === 'Enter' || event.key === ' ') {\n event.preventDefault();\n setExpandedMetric(isExpanded ? null : metric.id);\n }\n }}\n className=\"cursor-pointer rounded-lg p-4 focus:outline-none focus:ring-2 focus:ring-action-primary-bg\"\n aria-expanded={isExpanded}\n aria-label={`${metric.name} metric details`}\n >\n <div className=\"flex items-start justify-between gap-2\">\n <div className=\"min-w-0 flex-1\">\n <p className=\"truncate text-sm font-medium text-text-primary\">\n {metric.name}\n </p>\n <p className=\"mt-0.5 flex items-center gap-1.5 text-xs text-text-muted\">\n <span>\n {metric.direction === 'LOWER_IS_BETTER'\n ? '↓ lower better'\n : '↑ higher better'}\n </span>\n {metric.weight && metric.weight !== 1 ? (\n <span>· ×{metric.weight}</span>\n ) : null}\n {metric.statusOverride ? <span>· pinned</span> : null}\n </p>\n </div>\n <div className=\"flex flex-shrink-0 items-center gap-1\">\n <button\n type=\"button\"\n onClick={(event) => handleEditMetricClick(event, metric)}\n className=\"rounded-md p-1 text-text-secondary hover:bg-bg-sunken hover:text-text-primary focus:outline-none focus:ring-2 focus:ring-action-primary-bg\"\n aria-label={tr('scorecard.editMetric', 'Edit metric')}\n title={tr('scorecard.editMetric', 'Edit metric')}\n >\n <Pencil className=\"size-4\" />\n </button>\n <button\n type=\"button\"\n onClick={(event) => handleRemoveMetricClick(event, metric)}\n className=\"rounded-md p-1 text-text-secondary hover:bg-status-error-bg hover:text-status-error-text focus:outline-none focus:ring-2 focus:ring-status-error-text\"\n aria-label={tr('scorecard.removeMetric', 'Remove metric')}\n title={tr('scorecard.removeMetric', 'Remove metric')}\n >\n <Trash2 className=\"size-4\" />\n </button>\n <div\n className={`flex size-7 items-center justify-center rounded-full ${config.bg}`}\n >\n <StatusIcon className={`size-4 ${config.text}`} />\n </div>\n </div>\n </div>\n\n {/* Value and progress */}\n <div className=\"mt-3\">\n <div className=\"flex items-baseline justify-between\">\n <span className=\"text-lg font-bold text-text-primary\">\n {value}\n {metric.unit ? ` ${metric.unit}` : ''}\n </span>\n <span className=\"text-xs text-text-muted\">\n / {threshold}\n {metric.unit ? ` ${metric.unit}` : ''}\n </span>\n </div>\n <div className=\"mt-1.5 h-1.5 w-full overflow-hidden rounded-full bg-bg-sunken\">\n <motion.div\n initial={{ width: 0 }}\n animate={{ width: `${percentage}%` }}\n transition={{ duration: 0.5, ease: 'easeOut' }}\n className={`h-full rounded-full ${config.bar}`}\n />\n </div>\n </div>\n\n {showRemediation && (\n <p className=\"mt-2 rounded-md border border-status-warning-border bg-status-warning-bg-subtle px-2 py-1.5 text-xs text-status-warning-text\">\n {metric.remediation}\n </p>\n )}\n </div>\n\n {/* Expanded: trend + optional embed */}\n <AnimatePresence>\n {isExpanded && (\n <motion.div\n initial={{ height: 0, opacity: 0 }}\n animate={{ height: 'auto', opacity: 1 }}\n exit={{ height: 0, opacity: 0 }}\n transition={{ duration: 0.2 }}\n className=\"overflow-hidden border-t border-border-default\"\n >\n <div className=\"px-4 py-3\">\n <MetricTrend metricId={metric.id} colorClass={config.text} />\n </div>\n {metric.iframeUrl && (\n <iframe\n src={metric.iframeUrl}\n className=\"w-full border-0\"\n style={{\n height: metric.iframeHeight ? `${metric.iframeHeight}px` : '200px',\n }}\n title={`${metric.name} embed`}\n sandbox=\"allow-scripts allow-same-origin\"\n />\n )}\n </motion.div>\n )}\n </AnimatePresence>\n </Card>\n </MotionItem>\n );\n })}\n </MotionList>\n\n <ScorecardsOverviewPanel ownerType={ownerType} highlightOwnerId={ownerId} />\n\n {renderAddMetricDialog()}\n {renderDeleteMetricDialog()}\n </div>\n );\n};\n\nconst TIER_STYLE: Record<ScorecardTier, { label: string; cls: string }> = {\n [ScorecardTier.Bronze]: {\n label: 'Bronze',\n cls: 'bg-status-warning-bg-subtle text-status-warning-text border border-status-warning-border',\n },\n [ScorecardTier.Silver]: {\n label: 'Silver',\n cls: 'bg-bg-sunken text-text-secondary border border-border-strong',\n },\n [ScorecardTier.Gold]: {\n label: 'Gold',\n cls: 'bg-status-success-bg-subtle text-status-success-text border border-status-success-border',\n },\n};\n\n/**\n * TierBadge — shows the scorecard's derived production-readiness tier (Bronze/Silver/Gold).\n * The tier is computed server-side from the tier-tagged metrics; this is read-only.\n */\nconst TierBadge: FC<{ tier: ScorecardTier }> = ({ tier }) => {\n const style = TIER_STYLE[tier];\n return (\n <span\n className={`inline-flex items-center gap-1 rounded-full px-2.5 py-1 text-xs font-semibold ${style.cls}`}\n title={`Security readiness tier: ${style.label}`}\n >\n <ShieldCheck className=\"size-3.5\" />\n {style.label}\n </span>\n );\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAwEA,IAAM,IAAuC;CAC3C,MAAM;CACN,aAAa;CACb,MAAM;CACN,OAAO;CACP,WAAW;CACX,MAAM;CACN,WAAW;CACX,QAAQ;CACR,YAAY;CACZ,aAAa;CACb,WAAW;CACX,cAAc;CACf,EAEK,IACJ,gJAEW,KAA2C,EAAE,cAAW,YAAS,qBAAkB;CAC9F,IAAM,EAAE,SAAM,IAAS,EACjB,IAAK,GACR,GAAa,MAA6B;EACzC,IAAM,IAAI,EAAE,EAAI;AAChB,SAAO,MAAM,IAAM,IAAW;IAEhC,CAAC,EAAE,CACJ,EAEK,EAAE,oBAAgB,IAAkB,EAEpC,CAAC,IAAgB,KAAqB,EAAwB,KAAK,EACnE,CAAC,IAAiB,KAAsB,EAAS,GAAM,EACvD,CAAC,GAAiB,KAAsB,EAAwB,KAAK,EACrE,CAAC,GAAgB,KAAqB,EAA8C,KAAK,EACzF,CAAC,GAAa,KAAkB,EAAwB,KAAK,EAC7D,CAAC,GAAY,KAAiB,EAA0B,EAAoB,EAC5E,CAAC,GAAW,KAAgB,EAAwB,KAAK,EACzD,CAAC,IAAoB,KAAyB,EAAwB,KAAK,EAE3E,EAAE,UAAM,aAAS,UAAO,eAAY,EAAqB;EAC7D,WAAW;GAAE;GAAW;GAAS;EACjC,MAAM,CAAC;EACP,aAAa;EACd,CAAC,EAEI,CAAC,IAAiB,EAAE,SAAS,QAAwB,GAA4B,EACjF,CAAC,IAAoB,EAAE,SAAS,QAAkB,GAA+B,EACjF,CAAC,IAAuB,EAAE,SAAS,QAAoB,GAAkC,EACzF,CAAC,IAAuB,EAAE,SAAS,OAAoB,GAAkC,EACzF,CAAC,MAAiB,GAAmC,EAErD,IAAY,IAAM,yBAAyB,MAC3C,IAAiB,MAAsB,MAAgB,IACvD,IAAY,MAAoB,MAEhC,UAA8B;AAGlC,EAFA,EAAc,EAAoB,EAClC,EAAa,KAAK,EAClB,EAAmB,KAAK;IAGpB,UAAmC;AACvC,MAAI,GAAa;AACf,MAAa;AACb;;AAGF,EADA,GAAiB,EACjB,EAAmB,GAAK;IAGpB,MAAyB,GAAmB,MAAkC;AAsBlF,EArBA,EAAM,iBAAiB,EACvB,EAAc;GACZ,MAAM,EAAO;GACb,aAAa,EAAO,eAAe;GACnC,MAAM,EAAO;GACb,OAAO,EAAO,UAAU,QAAQ,EAAO,UAAU,KAAA,IAAY,OAAO,EAAO,MAAM,GAAG;GACpF,WACE,EAAO,cAAc,QAAQ,EAAO,cAAc,KAAA,IAAY,OAAO,EAAO,UAAU,GAAG;GAC3F,MAAM,EAAO,QAAQ;GACrB,WAAW,EAAO,aAAa;GAC/B,QAAQ,EAAO,WAAW,QAAQ,EAAO,WAAW,KAAA,IAAY,OAAO,EAAO,OAAO,GAAG;GACxF,YAAY,EAAO,kBAAkB;GACrC,aAAa,EAAO,eAAe;GACnC,WAAW,EAAO,aAAa;GAC/B,cACE,EAAO,iBAAiB,QAAQ,EAAO,iBAAiB,KAAA,IACpD,OAAO,EAAO,aAAa,GAC3B;GACP,CAAC,EACF,EAAa,KAAK,EAClB,EAAmB,EAAO,GAAG,EAC7B,EAAmB,GAAK;IAGpB,MAA2B,GAAmB,MAAkC;AAGpF,EAFA,EAAM,iBAAiB,EACvB,EAAe,KAAK,EACpB,EAAkB;GAAE,IAAI,EAAO;GAAI,MAAM,EAAO;GAAM,CAAC;IAGnD,UAAmC;AACnC,QACJ,EAAmB,GAAM,EACzB,GAAiB;IAGb,IAAoB,QAAwB;AAC5C,QACJ,EAAkB,KAAK,EACvB,EAAe,KAAK;IACnB,CAAC,EAAe,CAAC;AAEpB,UAAgB;AACd,MAAI,CAAC,EAAgB;EACrB,IAAM,KAAW,MAA+B;AAC9C,GAAI,EAAM,QAAQ,YAAU,GAAmB;;AAGjD,SADA,OAAO,iBAAiB,WAAW,EAAQ,QAC9B,OAAO,oBAAoB,WAAW,EAAQ;IAC1D,CAAC,GAAgB,EAAkB,CAAC;CAEvC,IAAM,KAAsB,YAA2B;AAChD,SACL;KAAe,KAAK;AACpB,OAAI;AAGF,IAFA,MAAM,GAAsB,EAAE,WAAW,EAAE,IAAI,EAAe,IAAI,EAAE,CAAC,EACrE,MAAM,GAAS,EACf,EAAkB,KAAK;YAChB,GAAa;AACpB,MACE,aAAuB,QAAQ,EAAY,UAAU,0BACtD;;;IAIC,KAAoB,GAA8B,MAAwB;AAC9E,KAAe,OAAa;GAAE,GAAG;IAAU,IAAQ;GAAO,EAAE;IAGxD,KAAuB,GAAe,MAAsC;EAChF,IAAM,IAAU,EAAM,MAAM;AAC5B,MAAI,CAAC,EAAS;EACd,IAAM,IAAS,OAAO,EAAQ;AAC9B,MAAI,CAAC,OAAO,SAAS,EAAO,CAAE,OAAU,MAAM,GAAG,EAAM,yBAAyB;AAChF,SAAO;IAGH,KAAoB,YAA6B;AACrD,MAAI,GAAW,GAAI,QAAO,EAAU;EAEpC,IAAM,KADU,MAAM,GAAgB,EAAE,WAAW,EAAE,OAAO;GAAE;GAAW;GAAS,EAAE,EAAE,CAAC,EACpE,MAAM,4BAA4B;AACrD,MAAI,CAAC,EAAI,OAAU,MAAM,EAAG,mCAAmC,6BAA6B,CAAC;AAC7F,SAAO;IAGH,KAAsB,OAAO,MAAsC;AACvE,IAAsB,EAAW;AACjC,MAAI;AAEF,GADA,MAAM,GAAc,EAAE,WAAW;IAAE;IAAY;IAAW;IAAS,EAAE,CAAC,EACtE,MAAM,GAAS;YACP;AACR,KAAsB,KAAK;;IAIzB,KAAwB,OAAO,MAAqD;AAExF,EADA,EAAM,gBAAgB,EACtB,EAAa,KAAK;EAElB,IAAM,IAAO,EAAW,KAAK,MAAM;AACnC,MAAI,CAAC,GAAM;AACT,KAAa,EAAG,gCAAgC,0BAA0B,CAAC;AAC3E;;EAIF,IAAM,IACJ,EAAW,eAAe,SACtB,KAAA,IACC,EAAW;AAElB,MAAI;GACF,IAAM,IAAc;IAClB;IACA,aAAa,EAAW,YAAY,MAAM,IAAI;IAC9C,MAAM,EAAW;IACjB,OAAO,EAAoB,EAAW,OAAO,EAAG,mBAAmB,QAAQ,CAAC,IAAI;IAChF,WACE,EAAoB,EAAW,WAAW,EAAG,uBAAuB,YAAY,CAAC,IAAI;IACvF,MAAM,EAAW,KAAK,MAAM,IAAI;IAChC,WAAW,EAAW;IACtB,QAAQ,EAAoB,EAAW,QAAQ,EAAG,oBAAoB,SAAS,CAAC,IAAI;IACpF,QAAQ,KAAkB;IAC1B,aAAa,EAAW,YAAY,MAAM,IAAI;IAC9C,WAAW,EAAW,UAAU,MAAM,IAAI;IAC1C,cACE,EACE,EAAW,cACX,EAAG,0BAA0B,gBAAgB,CAC9C,IAAI;IACR;AAWD,GATI,IACF,MAAM,GAAsB,EAAE,WAAW;IAAE,IAAI;IAAiB,OAAO;IAAa,EAAE,CAAC,GAGvF,MAAM,GAAmB,EAAE,WAAW;IAAE,aADpB,MAAM,IAAmB;IACQ,OAAO;IAAa,EAAE,CAAC,EAG9E,MAAM,GAAS,EACf,EAAmB,GAAM,EACzB,GAAiB;WACV,GAAa;AACpB,KACE,aAAuB,QACnB,EAAY,UACZ,IACE,4BACA,uBACP;;IAKC,KACJ,EAAW,eAAe,SACtB,GACE,EAAW,MAAM,MAAM,GAAG,OAAO,EAAW,MAAM,GAAG,MACrD,EAAW,UAAU,MAAM,GAAG,OAAO,EAAW,UAAU,GAAG,MAC7D,EAAW,UACZ,GACA,EAAW,YAEZ,UACJ,kBAAC,GAAD,EAAA,UACG,KACC,kBAAC,EAAO,KAAR;EACE,SAAS,EAAE,SAAS,GAAG;EACvB,SAAS,EAAE,SAAS,GAAG;EACvB,MAAM,EAAE,SAAS,GAAG;EACpB,WAAU;EACV,SAAS;YAET,kBAAC,EAAO,KAAR;GACE,SAAS;IAAE,OAAO;IAAM,GAAG;IAAI;GAC/B,SAAS;IAAE,OAAO;IAAG,GAAG;IAAG;GAC3B,MAAM;IAAE,OAAO;IAAM,GAAG;IAAI;GAC5B,WAAU;GACV,UAAU,MAAU,EAAM,iBAAiB;GAC3C,MAAK;GACL,cAAW;GACX,mBAAgB;aARlB;IAUE,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,MAAD;MAAI,IAAG;MAAyB,WAAU;gBACvC,EAAG,+BAA+B,gBAAgB;MAChD,CAAA,EACL,kBAAC,KAAD;MAAG,WAAU;gBACV,EACC,iCACA,sEACD;MACC,CAAA,CACA;;IACN,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,KAAD;MAAG,WAAU;gBAAe,EAAe;MAAS,CAAA,EACnD,KACC,kBAAC,KAAD;MAAG,WAAU;gBACV;MACC,CAAA,CAEF;;IACN,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,UAAD;MACE,MAAK;MACL,SAAS;MACT,UAAU;MACV,WAAU;gBAET,EAAG,iBAAiB,SAAS;MACvB,CAAA,EACT,kBAAC,UAAD;MACE,MAAK;MACL,SAAS;MACT,UAAU;MACV,WAAU;gBAET,IACG,EAAG,4BAA4B,cAAc,GAC7C,EAAG,0BAA0B,gBAAgB;MAC1C,CAAA,CACL;;IACK;;EACF,CAAA,EAEC,CAAA,EAGd,UAA8B;EAClC,IAAM,IAAc,EAAa,OAAkB,EAAa;AAChE,SACE,kBAAC,GAAD,EAAA,UACG,MACC,kBAAC,EAAO,KAAR;GACE,SAAS,EAAE,SAAS,GAAG;GACvB,SAAS,EAAE,SAAS,GAAG;GACvB,MAAM,EAAE,SAAS,GAAG;GACpB,WAAU;GACV,SAAS;aAET,kBAAC,EAAO,KAAR;IACE,SAAS;KAAE,OAAO;KAAM,GAAG;KAAI;IAC/B,SAAS;KAAE,OAAO;KAAG,GAAG;KAAG;IAC3B,MAAM;KAAE,OAAO;KAAM,GAAG;KAAI;IAC5B,WAAU;IACV,UAAU,MAAU,EAAM,iBAAiB;cAE3C,kBAAC,QAAD;KAAM,UAAU;KAAuB,WAAU;eAAjD;MACE,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,MAAD;QAAI,WAAU;kBACX,IACG,EAAG,6BAA6B,wBAAwB,GACxD,EAAG,4BAA4B,uBAAuB;QACvD,CAAA,EACL,kBAAC,KAAD;QAAG,WAAU;kBACV,EACC,kCACA,gFACD;QACC,CAAA,CACA;;MAEN,kBAAC,OAAD;OAAK,WAAU;iBAAf;QACG,KACC,kBAAC,OAAD;SAAK,WAAU;mBACZ;SACG,CAAA;QAGR,kBAAC,SAAD;SAAO,WAAU;mBAAjB,CACE,kBAAC,QAAD;UAAM,WAAU;oBACb,EAAG,wBAAwB,cAAc;UACrC,CAAA,EACP,kBAAC,SAAD;UACE,cAAW;UACX,OAAO,EAAW;UAClB,WAAW,MAAU,EAAiB,QAAQ,EAAM,OAAO,MAAM;UACjE,WAAW;UACX,aAAa,EAAG,mCAAmC,gBAAgB;UACnE,CAAA,CACI;;QAER,kBAAC,SAAD;SAAO,WAAU;mBAAjB,CACE,kBAAC,QAAD;UAAM,WAAU;oBACb,EAAG,+BAA+B,cAAc;UAC5C,CAAA,EACP,kBAAC,YAAD;UACE,cAAW;UACX,OAAO,EAAW;UAClB,WAAW,MAAU,EAAiB,eAAe,EAAM,OAAO,MAAM;UACxE,WAAW,YAAY;UACvB,aAAa,EACX,0CACA,oCACD;UACD,CAAA,CACI;;QAER,kBAAC,OAAD;SAAK,WAAU;mBAAf,CACE,kBAAC,SAAD;UAAO,WAAU;oBAAjB,CACE,kBAAC,QAAD;WAAM,WAAU;qBACb,EAAG,wBAAwB,OAAO;WAC9B,CAAA,EACP,kBAAC,UAAD;WACE,OAAO,EAAW;WAClB,WAAW,MACT,EAAiB,QAAQ,EAAM,OAAO,MAA6B;WAErE,WAAW;qBAEV,GAAoB,KAAK,MACxB,kBAAC,UAAD;YAA2B,OAAO,EAAO;sBACtC,EAAO;YACD,EAFI,EAAO,MAEX,CACT;WACK,CAAA,CACH;aAER,kBAAC,SAAD;UAAO,WAAU;oBAAjB,CACE,kBAAC,QAAD;WAAM,WAAU;qBACb,EAAG,6BAA6B,YAAY;WACxC,CAAA,EACP,kBAAC,UAAD;WACE,OAAO,EAAW;WAClB,WAAW,MACT,EACE,aACA,EAAM,OAAO,MACd;WAEH,WAAW;qBAEV,EAAkB,KAAK,MACtB,kBAAC,UAAD;YAA2B,OAAO,EAAO;sBACtC,EAAO;YACD,EAFI,EAAO,MAEX,CACT;WACK,CAAA,CACH;YACJ;;QAEN,kBAAC,OAAD;SAAK,WAAU;mBAAf;UACE,kBAAC,SAAD;WAAO,WAAU;qBAAjB,CACE,kBAAC,QAAD;YAAM,WAAU;sBACb,EAAG,mBAAmB,QAAQ;YAC1B,CAAA,EACP,kBAAC,SAAD;YACE,cAAW;YACX,OAAO,EAAW;YAClB,WAAW,MAAU,EAAiB,SAAS,EAAM,OAAO,MAAM;YAClE,WAAU;YACV,WAAW;YACX,aAAY;YACZ,CAAA,CACI;;UAER,kBAAC,SAAD;WAAO,WAAU;qBAAjB,CACE,kBAAC,QAAD;YAAM,WAAU;sBACb,EAAG,uBAAuB,YAAY;YAClC,CAAA,EACP,kBAAC,SAAD;YACE,cAAW;YACX,OAAO,EAAW;YAClB,WAAW,MAAU,EAAiB,aAAa,EAAM,OAAO,MAAM;YACtE,WAAU;YACV,WAAW;YACX,aAAY;YACZ,CAAA,CACI;;UAER,kBAAC,SAAD;WAAO,WAAU;qBAAjB,CACE,kBAAC,QAAD;YAAM,WAAU;sBACb,EAAG,kBAAkB,OAAO;YACxB,CAAA,EACP,kBAAC,SAAD;YACE,cAAW;YACX,OAAO,EAAW;YAClB,WAAW,MAAU,EAAiB,QAAQ,EAAM,OAAO,MAAM;YACjE,WAAW;YACX,aAAY;YACZ,CAAA,CACI;;UAER,kBAAC,SAAD;WAAO,WAAU;qBAAjB,CACE,kBAAC,QAAD;YAAM,WAAU;sBACb,EAAG,oBAAoB,SAAS;YAC5B,CAAA,EACP,kBAAC,SAAD;YACE,cAAW;YACX,OAAO,EAAW;YAClB,WAAW,MAAU,EAAiB,UAAU,EAAM,OAAO,MAAM;YACnE,WAAU;YACV,WAAW;YACX,aAAY;YACZ,CAAA,CACI;;UACJ;;QAEN,kBAAC,OAAD;SAAK,WAAU;mBAAf,CACE,kBAAC,SAAD;UAAO,WAAU;oBAAjB,CACE,kBAAC,QAAD;WAAM,WAAU;qBACb,EAAG,0BAA0B,SAAS;WAClC,CAAA,EACP,kBAAC,UAAD;WACE,OAAO,EAAW;WAClB,WAAW,MACT,EAAiB,cAAc,EAAM,OAAO,MAAoB;WAElE,WAAW;qBAEV,GAAe,KAAK,MACnB,kBAAC,UAAD;YAA2B,OAAO,EAAO;sBACtC,EAAO;YACD,EAFI,EAAO,MAEX,CACT;WACK,CAAA,CACH;aAER,kBAAC,OAAD;UAAK,WAAU;oBACb,kBAAC,OAAD;WACE,WAAW,wDAAwD,EAAY,GAAG,GAAG,EAAY;qBADnG,CAGE,kBAAC,EAAY,MAAb,EAAkB,WAAU,UAAW,CAAA,EACvC,kBAAC,QAAD,EAAA,UAAA,CACG,EAAW,eAAe,SAAS,cAAc,YACjD,EAAY,MACR,EAAA,CAAA,CACH;;UACF,CAAA,CACF;;QAEN,kBAAC,SAAD;SAAO,WAAU;mBAAjB,CACE,kBAAC,QAAD;UAAM,WAAU;oBACb,EAAG,yBAAyB,uBAAuB;UAC/C,CAAA,EACP,kBAAC,YAAD;UACE,cAAW;UACX,OAAO,EAAW;UAClB,WAAW,MAAU,EAAiB,eAAe,EAAM,OAAO,MAAM;UACxE,WAAW,YAAY;UACvB,aAAa,EACX,oCACA,sDACD;UACD,CAAA,CACI;;QAER,kBAAC,OAAD;SAAK,WAAU;mBAAf,CACE,kBAAC,SAAD;UAAO,WAAU;oBAAjB,CACE,kBAAC,QAAD;WAAM,WAAU;qBACb,EAAG,uBAAuB,YAAY;WAClC,CAAA,EACP,kBAAC,SAAD;WACE,cAAW;WACX,OAAO,EAAW;WAClB,WAAW,MAAU,EAAiB,aAAa,EAAM,OAAO,MAAM;WACtE,WAAW;WACX,aAAa,EAAG,kCAAkC,yBAAyB;WAC3E,CAAA,CACI;aAER,kBAAC,SAAD;UAAO,WAAU;oBAAjB,CACE,kBAAC,QAAD;WAAM,WAAU;qBACb,EAAG,0BAA0B,gBAAgB;WACzC,CAAA,EACP,kBAAC,SAAD;WACE,cAAW;WACX,OAAO,EAAW;WAClB,WAAW,MAAU,EAAiB,gBAAgB,EAAM,OAAO,MAAM;WACzE,WAAU;WACV,WAAW;WACX,aAAY;WACZ,CAAA,CACI;YACJ;;QACF;;MAEN,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,UAAD;QACE,MAAK;QACL,SAAS;QACT,UAAU;QACV,WAAU;kBAET,EAAG,iBAAiB,SAAS;QACvB,CAAA,EACT,kBAAC,UAAD;QACE,MAAK;QACL,UAAU;QACV,WAAU;kBAET,IACG,EAAG,0BAA0B,YAAY,GACzC,IACE,EAAG,0BAA0B,gBAAgB,GAC7C,EAAG,wBAAwB,cAAc;QACxC,CAAA,CACL;;MACD;;IACI,CAAA;GACF,CAAA,EAEC,CAAA;;AAItB,KAAI,MAAW,CAAC,EACd,QAAO,kBAAC,GAAD,EAAc,SAAS,EAAG,qBAAqB,uBAAuB,EAAI,CAAA;AAGnF,KAAI,KAAS,CAAC,EACZ,QACE,kBAAC,GAAD;EACE,OAAO,EAAG,wBAAwB,2BAA2B;EAC7D,SAAS,EAAM;EACf,eAAe,GAAS;EACxB,CAAA;AAIN,KAAI,CAAC,KAAa,EAAU,QAAQ,WAAW,EAC7C,QACE,kBAAA,IAAA,EAAA,UAAA;EACE,kBAAC,IAAD;GACa;GACF;GACI;GACO;GACpB,iBAAiB;GACjB,aAAa;GACb,CAAA;EACD,GAAuB;EACvB,GAA0B;EAC1B,EAAA,CAAA;CAIP,IAAM,IAAe,EAAU,gBAAgB,GACzC,IACJ,KAAgB,KACZ,6BACA,KAAgB,KACd,6BACA;AAER,QACE,kBAAC,OAAD;EAAK,WAAU;YAAf;GAEE,kBAAC,GAAD;IAAM,SAAQ;cACZ,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,KAAD;MAAG,WAAU;gBAAb,CACG,EAAG,0BAA0B,gBAAgB,EAC9C,kBAAC,QAAD;OAAM,OAAM;iBACV,kBAAC,IAAD,EAAM,WAAU,4BAA6B,CAAA;OACxC,CAAA,CACL;SACJ,kBAAC,KAAD;MAAG,WAAW,sBAAsB;gBAApC,CACG,GACD,kBAAC,QAAD;OAAM,WAAU;iBAA0B;OAAW,CAAA,CACnD;QACA,EAAA,CAAA,EACN,kBAAC,OAAD;MAAK,WAAU;gBAAf;OACG,EAAU,gBAAgB,kBAAC,IAAD,EAAW,MAAM,EAAU,cAAgB,CAAA;OACtE,kBAAC,IAAD,EAAY,WAAW,UAAU,KAAgB,CAAA;OACjD,kBAAC,IAAD;QACE,QAAO;QACP,UAAU;SACR,IAAI,EAAU;SAEd,MAAM,EAAU;SAChB,OAAO;UAAE;UAAS;UAAW;SAC9B;QACD,OAAO,EAAG,mBAAmB,QAAQ;QACrC,CAAA;OACF,kBAAC,UAAD;QACE,MAAK;QACL,eAAe,GAAS;QACxB,WAAU;QACV,OAAO,EAAG,kBAAkB,UAAU;kBAEtC,kBAAC,IAAD,EAAW,WAAU,UAAW,CAAA;QACzB,CAAA;OACT,kBAAC,UAAD;QACE,MAAK;QACL,SAAS;QACT,WAAU;kBAHZ,CAKE,kBAAC,IAAD,EAAM,WAAU,UAAW,CAAA,EAC1B,EAAG,uBAAuB,aAAa,CACjC;;OACL;QACF;;IACD,CAAA;GAGP,kBAAC,IAAD;IAAY,SAAS;IAAM,WAAU;cAClC,EAAU,QAAQ,KAAK,MAA4B;KAClD,IAAM,IAAS,EAAa,EAAO,WAAW,EAAa,SACrD,IAAa,EAAO,MACpB,IAAQ,EAAO,SAAS,GACxB,IAAY,EAAO,aAAa,KAChC,IACJ,IAAY,IAAI,KAAK,IAAI,KAAK,KAAK,IAAI,GAAI,IAAQ,IAAa,IAAI,CAAC,GAAG,GACpE,IAAa,OAAmB,EAAO,IACvC,IACJ,EAAO,gBAAgB,EAAO,WAAW,aAAa,EAAO,WAAW;AAE1E,YACE,kBAAC,IAAD,EAAA,UACE,kBAAC,GAAD;MAAM,WAAA;MAAU,SAAQ;gBAAxB,CACE,kBAAC,OAAD;OACE,MAAK;OACL,UAAU;OACV,eAAe,EAAkB,IAAa,OAAO,EAAO,GAAG;OAC/D,YAAY,MAAU;AAChB,UAAM,WAAW,EAAM,kBACvB,EAAM,QAAQ,WAAW,EAAM,QAAQ,SACzC,EAAM,gBAAgB,EACtB,EAAkB,IAAa,OAAO,EAAO,GAAG;;OAGpD,WAAU;OACV,iBAAe;OACf,cAAY,GAAG,EAAO,KAAK;iBAb7B;QAeE,kBAAC,OAAD;SAAK,WAAU;mBAAf,CACE,kBAAC,OAAD;UAAK,WAAU;oBAAf,CACE,kBAAC,KAAD;WAAG,WAAU;qBACV,EAAO;WACN,CAAA,EACJ,kBAAC,KAAD;WAAG,WAAU;qBAAb;YACE,kBAAC,QAAD,EAAA,UACG,EAAO,cAAc,oBAClB,mBACA,mBACC,CAAA;YACN,EAAO,UAAU,EAAO,WAAW,IAClC,kBAAC,QAAD,EAAA,UAAA,CAAM,OAAI,EAAO,OAAc,EAAA,CAAA,GAC7B;YACH,EAAO,iBAAiB,kBAAC,QAAD,EAAA,UAAM,YAAe,CAAA,GAAG;YAC/C;aACA;aACN,kBAAC,OAAD;UAAK,WAAU;oBAAf;WACE,kBAAC,UAAD;YACE,MAAK;YACL,UAAU,MAAU,GAAsB,GAAO,EAAO;YACxD,WAAU;YACV,cAAY,EAAG,wBAAwB,cAAc;YACrD,OAAO,EAAG,wBAAwB,cAAc;sBAEhD,kBAAC,IAAD,EAAQ,WAAU,UAAW,CAAA;YACtB,CAAA;WACT,kBAAC,UAAD;YACE,MAAK;YACL,UAAU,MAAU,GAAwB,GAAO,EAAO;YAC1D,WAAU;YACV,cAAY,EAAG,0BAA0B,gBAAgB;YACzD,OAAO,EAAG,0BAA0B,gBAAgB;sBAEpD,kBAAC,IAAD,EAAQ,WAAU,UAAW,CAAA;YACtB,CAAA;WACT,kBAAC,OAAD;YACE,WAAW,wDAAwD,EAAO;sBAE1E,kBAAC,GAAD,EAAY,WAAW,UAAU,EAAO,QAAU,CAAA;YAC9C,CAAA;WACF;YACF;;QAGN,kBAAC,OAAD;SAAK,WAAU;mBAAf,CACE,kBAAC,OAAD;UAAK,WAAU;oBAAf,CACE,kBAAC,QAAD;WAAM,WAAU;qBAAhB,CACG,GACA,EAAO,OAAO,IAAI,EAAO,SAAS,GAC9B;cACP,kBAAC,QAAD;WAAM,WAAU;qBAAhB;YAA0C;YACrC;YACF,EAAO,OAAO,IAAI,EAAO,SAAS;YAC9B;aACH;aACN,kBAAC,OAAD;UAAK,WAAU;oBACb,kBAAC,EAAO,KAAR;WACE,SAAS,EAAE,OAAO,GAAG;WACrB,SAAS,EAAE,OAAO,GAAG,EAAW,IAAI;WACpC,YAAY;YAAE,UAAU;YAAK,MAAM;YAAW;WAC9C,WAAW,uBAAuB,EAAO;WACzC,CAAA;UACE,CAAA,CACF;;QAEL,KACC,kBAAC,KAAD;SAAG,WAAU;mBACV,EAAO;SACN,CAAA;QAEF;UAGN,kBAAC,GAAD,EAAA,UACG,KACC,kBAAC,EAAO,KAAR;OACE,SAAS;QAAE,QAAQ;QAAG,SAAS;QAAG;OAClC,SAAS;QAAE,QAAQ;QAAQ,SAAS;QAAG;OACvC,MAAM;QAAE,QAAQ;QAAG,SAAS;QAAG;OAC/B,YAAY,EAAE,UAAU,IAAK;OAC7B,WAAU;iBALZ,CAOE,kBAAC,OAAD;QAAK,WAAU;kBACb,kBAAC,IAAD;SAAa,UAAU,EAAO;SAAI,YAAY,EAAO;SAAQ,CAAA;QACzD,CAAA,EACL,EAAO,aACN,kBAAC,UAAD;QACE,KAAK,EAAO;QACZ,WAAU;QACV,OAAO,EACL,QAAQ,EAAO,eAAe,GAAG,EAAO,aAAa,MAAM,SAC5D;QACD,OAAO,GAAG,EAAO,KAAK;QACtB,SAAQ;QACR,CAAA,CAEO;UAEC,CAAA,CACb;SACI,EAtHI,EAAO,GAsHX;MAEf;IACS,CAAA;GAEb,kBAAC,GAAD;IAAoC;IAAW,kBAAkB;IAAW,CAAA;GAE3E,GAAuB;GACvB,GAA0B;GACvB;;GAIJ,IAAoE;EACvE,EAAc,SAAS;EACtB,OAAO;EACP,KAAK;EACN;EACA,EAAc,SAAS;EACtB,OAAO;EACP,KAAK;EACN;EACA,EAAc,OAAO;EACpB,OAAO;EACP,KAAK;EACN;CACF,EAMK,MAA0C,EAAE,cAAW;CAC3D,IAAM,IAAQ,EAAW;AACzB,QACE,kBAAC,QAAD;EACE,WAAW,iFAAiF,EAAM;EAClG,OAAO,4BAA4B,EAAM;YAF3C,CAIE,kBAAC,GAAD,EAAa,WAAU,YAAa,CAAA,EACnC,EAAM,MACF"}
@@ -5,15 +5,15 @@ import { Fragment as l, jsx as u, jsxs as d } from "react/jsx-runtime";
5
5
  //#region src/components/security/SecurityScanList.tsx
6
6
  var f = {
7
7
  queued: "bg-bg-sunken text-text-secondary",
8
- running: "bg-status-info-bg/20 text-status-info-text",
9
- succeeded: "bg-status-success-bg/20 text-status-success-text",
10
- failed: "bg-status-error-bg/20 text-status-error-text",
11
- errored: "bg-status-error-bg/20 text-status-error-text",
8
+ running: "bg-status-info-bg-subtle text-status-info-text",
9
+ succeeded: "bg-status-success-bg-subtle text-status-success-text",
10
+ failed: "bg-status-error-bg-subtle text-status-error-text",
11
+ errored: "bg-status-error-bg-subtle text-status-error-text",
12
12
  cancelled: "bg-bg-sunken text-text-tertiary"
13
13
  }, p = {
14
- pass: "bg-status-success-bg/20 text-status-success-text",
15
- warn: "bg-status-warning-bg/20 text-status-warning-text",
16
- fail: "bg-status-error-bg/20 text-status-error-text",
14
+ pass: "bg-status-success-bg-subtle text-status-success-text",
15
+ warn: "bg-status-warning-bg-subtle text-status-warning-text",
16
+ fail: "bg-status-error-bg-subtle text-status-error-text",
17
17
  skipped: "bg-bg-sunken text-text-tertiary"
18
18
  };
19
19
  function m(e) {
@@ -70,17 +70,17 @@ function v({ run: e }) {
70
70
  className: "flex items-center gap-1 text-xs",
71
71
  children: [
72
72
  t.critical > 0 && /* @__PURE__ */ d("span", {
73
- className: "inline-flex items-center gap-0.5 rounded px-1.5 py-0.5 bg-status-error-bg/20 text-status-error-text font-mono",
73
+ className: "inline-flex items-center gap-0.5 rounded px-1.5 py-0.5 bg-status-error-bg-subtle text-status-error-text font-mono",
74
74
  title: `${t.critical} critical`,
75
75
  children: ["C ", t.critical]
76
76
  }),
77
77
  t.high > 0 && /* @__PURE__ */ d("span", {
78
- className: "inline-flex items-center gap-0.5 rounded px-1.5 py-0.5 bg-status-warning-bg/20 text-status-warning-text font-mono",
78
+ className: "inline-flex items-center gap-0.5 rounded px-1.5 py-0.5 bg-status-warning-bg-subtle text-status-warning-text font-mono",
79
79
  title: `${t.high} high`,
80
80
  children: ["H ", t.high]
81
81
  }),
82
82
  t.medium > 0 && /* @__PURE__ */ d("span", {
83
- className: "inline-flex items-center gap-0.5 rounded px-1.5 py-0.5 bg-status-warning-bg/10 text-status-warning-text/80 font-mono",
83
+ className: "inline-flex items-center gap-0.5 rounded px-1.5 py-0.5 bg-status-warning-bg-subtle text-status-warning-text/80 font-mono",
84
84
  title: `${t.medium} medium`,
85
85
  children: ["M ", t.medium]
86
86
  }),
@@ -165,7 +165,7 @@ function b({ vibeId: r, agentId: i, profile: a, limit: o = 20 }) {
165
165
  })]
166
166
  }),
167
167
  x && /* @__PURE__ */ d("div", {
168
- className: "m-4 flex items-start gap-2 rounded-md border border-status-error-border bg-status-error-bg/10 p-3 text-sm",
168
+ className: "m-4 flex items-start gap-2 rounded-md border border-status-error-border bg-status-error-bg-subtle/10 p-3 text-sm",
169
169
  children: [/* @__PURE__ */ u(n, { className: "size-4 text-status-error-text mt-0.5 shrink-0" }), /* @__PURE__ */ d("div", {
170
170
  className: "text-text-primary",
171
171
  children: [/* @__PURE__ */ u("p", {
@@ -1 +1 @@
1
- {"version":3,"file":"SecurityScanList.js","names":[],"sources":["../../../src/components/security/SecurityScanList.tsx"],"sourcesContent":["/**\n * Recent scan runs list for a vibe, read directly from the agent over\n * the tunnel. No backend GraphQL — see `./agentClient.ts` for the\n * REST surface and `./hooks.ts` for the React Query wrappers.\n *\n * The agent doesn't expose a list-runs endpoint; we derive distinct\n * run ids from the findings cache (see `listRecentScanRuns`). When a\n * vibe has zero findings the list will be empty even if scans have\n * been queued — that's correct: an in-flight queued/running scan\n * doesn't yet have any findings, and the empty state already nudges\n * the user toward kicking off a scan.\n */\nimport { useMemo, type ReactElement } from 'react';\nimport {\n AlertCircle,\n CheckCircle2,\n Clock,\n Loader2,\n MinusCircle,\n ShieldAlert,\n XCircle,\n} from 'lucide-react';\n\nimport { useSecurityRecentScansQuery, type UseSecurityAgentContext } from './hooks';\nimport type { AgentScanConclusion, AgentScanStatus, SecurityScanRun } from './agentClient';\n\ninterface Props extends UseSecurityAgentContext {\n vibeId: string;\n /** Max rows to surface. Defaults to 20. */\n limit?: number;\n}\n\nconst STATUS_BADGE: Record<AgentScanStatus, string> = {\n queued: 'bg-bg-sunken text-text-secondary',\n running: 'bg-status-info-bg/20 text-status-info-text',\n succeeded: 'bg-status-success-bg/20 text-status-success-text',\n failed: 'bg-status-error-bg/20 text-status-error-text',\n errored: 'bg-status-error-bg/20 text-status-error-text',\n cancelled: 'bg-bg-sunken text-text-tertiary',\n};\n\nconst CONCLUSION_BADGE: Record<AgentScanConclusion, string> = {\n pass: 'bg-status-success-bg/20 text-status-success-text',\n warn: 'bg-status-warning-bg/20 text-status-warning-text',\n fail: 'bg-status-error-bg/20 text-status-error-text',\n skipped: 'bg-bg-sunken text-text-tertiary',\n};\n\nfunction statusIcon(status: AgentScanStatus): ReactElement {\n switch (status) {\n case 'queued':\n return <Clock className=\"size-3.5\" aria-hidden=\"true\" />;\n case 'running':\n return <Loader2 className=\"size-3.5 animate-spin\" aria-hidden=\"true\" />;\n case 'succeeded':\n return <CheckCircle2 className=\"size-3.5\" aria-hidden=\"true\" />;\n case 'failed':\n case 'errored':\n return <XCircle className=\"size-3.5\" aria-hidden=\"true\" />;\n case 'cancelled':\n return <MinusCircle className=\"size-3.5\" aria-hidden=\"true\" />;\n }\n}\n\nfunction relativeTime(epochMs?: number): string {\n if (!epochMs) return '—';\n const diff = Date.now() - epochMs;\n const sec = Math.round(diff / 1000);\n if (sec < 60) return `${sec}s ago`;\n const min = Math.round(sec / 60);\n if (min < 60) return `${min}m ago`;\n const hr = Math.round(min / 60);\n if (hr < 24) return `${hr}h ago`;\n const day = Math.round(hr / 24);\n return `${day}d ago`;\n}\n\nfunction formatDuration(ms?: number): string {\n if (ms === undefined || ms === null) return '—';\n if (ms < 1000) return `${ms}ms`;\n const sec = ms / 1000;\n if (sec < 60) return `${sec.toFixed(1)}s`;\n const min = sec / 60;\n if (min < 60) return `${min.toFixed(1)}m`;\n const hr = min / 60;\n return `${hr.toFixed(1)}h`;\n}\n\n/**\n * Prettify the agent's dotted stage id (e.g. `pull_request.fast` →\n * `pull_request fast`). We don't gate on the canonical 14-stage taxonomy\n * here because providers can — in principle — emit arbitrary stages and\n * we want to surface them rather than hide them.\n */\nfunction formatStage(stage: string): string {\n return stage.replace(/\\./g, ' ');\n}\n\nfunction SummaryCell({ run }: { run: SecurityScanRun }): ReactElement {\n const s = run.summary;\n const total = s.critical + s.high + s.medium + s.low + s.info;\n if (total === 0) {\n return <span className=\"text-xs text-text-tertiary\">clean</span>;\n }\n return (\n <div className=\"flex items-center gap-1 text-xs\">\n {s.critical > 0 && (\n <span\n className=\"inline-flex items-center gap-0.5 rounded px-1.5 py-0.5 bg-status-error-bg/20 text-status-error-text font-mono\"\n title={`${s.critical} critical`}\n >\n C {s.critical}\n </span>\n )}\n {s.high > 0 && (\n <span\n className=\"inline-flex items-center gap-0.5 rounded px-1.5 py-0.5 bg-status-warning-bg/20 text-status-warning-text font-mono\"\n title={`${s.high} high`}\n >\n H {s.high}\n </span>\n )}\n {s.medium > 0 && (\n <span\n className=\"inline-flex items-center gap-0.5 rounded px-1.5 py-0.5 bg-status-warning-bg/10 text-status-warning-text/80 font-mono\"\n title={`${s.medium} medium`}\n >\n M {s.medium}\n </span>\n )}\n {s.low > 0 && (\n <span\n className=\"inline-flex items-center gap-0.5 rounded px-1.5 py-0.5 bg-bg-sunken text-text-secondary font-mono\"\n title={`${s.low} low`}\n >\n L {s.low}\n </span>\n )}\n {s.info > 0 && (\n <span\n className=\"inline-flex items-center gap-0.5 rounded px-1.5 py-0.5 bg-bg-sunken text-text-tertiary font-mono\"\n title={`${s.info} info`}\n >\n I {s.info}\n </span>\n )}\n </div>\n );\n}\n\nfunction SkeletonRow({ rowKey }: { rowKey: number }): ReactElement {\n return (\n <tr key={rowKey} className=\"border-t border-border-subtle\">\n <td className=\"px-3 py-2\">\n <div className=\"h-3 w-20 rounded bg-bg-sunken animate-pulse\" />\n </td>\n <td className=\"px-3 py-2\">\n <div className=\"h-3 w-24 rounded bg-bg-sunken animate-pulse\" />\n </td>\n <td className=\"px-3 py-2\">\n <div className=\"h-3 w-16 rounded bg-bg-sunken animate-pulse\" />\n </td>\n <td className=\"px-3 py-2\">\n <div className=\"h-3 w-16 rounded bg-bg-sunken animate-pulse\" />\n </td>\n <td className=\"px-3 py-2\">\n <div className=\"h-3 w-20 rounded bg-bg-sunken animate-pulse\" />\n </td>\n <td className=\"px-3 py-2\">\n <div className=\"h-3 w-14 rounded bg-bg-sunken animate-pulse\" />\n </td>\n <td className=\"px-3 py-2\">\n <div className=\"h-3 w-28 rounded bg-bg-sunken animate-pulse\" />\n </td>\n </tr>\n );\n}\n\nexport function SecurityScanList({ vibeId, agentId, profile, limit = 20 }: Props): ReactElement {\n const ctx = useMemo(() => ({ agentId, profile }), [agentId, profile]);\n const { data, isLoading, error, isFetching, refetch } = useSecurityRecentScansQuery(\n ctx,\n vibeId,\n limit\n );\n\n // F4 Wave C: agentApiKey + tunnelUrl no longer needed in the browser —\n // the svc proxy owns them. A non-null `agentId` is the only readiness\n // gate (svc rejects unreachable agents with a structured envelope).\n const ready = !!agentId;\n\n if (!ready) {\n return (\n <section\n data-testid=\"security-scan-list\"\n className=\"rounded-lg border border-border-default bg-bg-surface p-4 text-sm text-text-secondary\"\n >\n Connect to a reachable agent to load scan history. Scan runs are read from the agent via the\n svc GraphQL proxy — there's no backend cache.\n </section>\n );\n }\n\n return (\n <section\n data-testid=\"security-scan-list\"\n className=\"rounded-lg border border-border-default bg-bg-surface\"\n >\n <header className=\"flex items-center justify-between gap-3 border-b border-border-default px-4 py-3\">\n <div className=\"min-w-0\">\n <h3 className=\"text-sm font-semibold text-text-primary\">Recent scan runs</h3>\n <p className=\"text-xs text-text-secondary mt-0.5\">\n Read live from the agent. Showing up to {limit} most recently active runs for this vibe.\n </p>\n </div>\n <button\n type=\"button\"\n onClick={() => void refetch()}\n disabled={isFetching}\n className=\"px-2.5 py-1 text-xs rounded-md border border-border-default bg-bg-sunken text-text-primary hover:bg-action-primary-bg-hover disabled:opacity-50\"\n >\n {isFetching ? 'Refreshing…' : 'Refresh'}\n </button>\n </header>\n\n {error && (\n <div className=\"m-4 flex items-start gap-2 rounded-md border border-status-error-border bg-status-error-bg/10 p-3 text-sm\">\n <AlertCircle className=\"size-4 text-status-error-text mt-0.5 shrink-0\" />\n <div className=\"text-text-primary\">\n <p className=\"font-medium\">Couldn't load scan runs</p>\n <p className=\"mt-0.5 text-xs text-text-secondary\">{error.message}</p>\n </div>\n </div>\n )}\n\n <div className=\"overflow-x-auto\">\n <table className=\"w-full text-sm\">\n <thead className=\"bg-bg-sunken/40 text-text-secondary\">\n <tr>\n <th className=\"px-3 py-2 text-left text-xs font-medium\">Stage</th>\n <th className=\"px-3 py-2 text-left text-xs font-medium\">Provider</th>\n <th className=\"px-3 py-2 text-left text-xs font-medium\">Status</th>\n <th className=\"px-3 py-2 text-left text-xs font-medium\">Conclusion</th>\n <th className=\"px-3 py-2 text-left text-xs font-medium\">Started</th>\n <th className=\"px-3 py-2 text-left text-xs font-medium\">Duration</th>\n <th className=\"px-3 py-2 text-left text-xs font-medium\">Findings</th>\n </tr>\n </thead>\n <tbody>\n {isLoading && data === undefined && (\n <>\n <SkeletonRow rowKey={0} />\n <SkeletonRow rowKey={1} />\n <SkeletonRow rowKey={2} />\n </>\n )}\n {!isLoading && (data?.length ?? 0) === 0 && !error && (\n <tr>\n <td colSpan={7} className=\"px-3 py-8\">\n <div className=\"flex flex-col items-center gap-2 text-center\">\n <ShieldAlert className=\"size-6 text-text-tertiary\" />\n <p className=\"text-sm text-text-primary\">No scans yet</p>\n <p className=\"text-xs text-text-secondary max-w-md\">\n Install a provider above and run a scan to see results here. Findings live in\n the agent's local SQLite cache.\n </p>\n </div>\n </td>\n </tr>\n )}\n {(data ?? []).map((run) => (\n <tr key={run.runId} className=\"border-t border-border-subtle align-middle\">\n <td className=\"px-3 py-2\">\n <span className=\"inline-flex items-center rounded-md bg-bg-sunken px-2 py-0.5 text-xs text-text-primary\">\n {formatStage(run.stage)}\n </span>\n </td>\n <td className=\"px-3 py-2 text-xs text-text-primary\">{run.providerName}</td>\n <td className=\"px-3 py-2\">\n <span\n className={`inline-flex items-center gap-1 rounded-md px-2 py-0.5 text-xs font-medium ${STATUS_BADGE[run.status]}`}\n >\n {statusIcon(run.status)}\n {run.status}\n </span>\n </td>\n <td className=\"px-3 py-2\">\n {run.conclusion ? (\n <span\n className={`inline-flex items-center rounded-md px-2 py-0.5 text-xs font-medium ${CONCLUSION_BADGE[run.conclusion]}`}\n >\n {run.conclusion}\n </span>\n ) : (\n <span className=\"text-xs text-text-tertiary\">—</span>\n )}\n </td>\n <td className=\"px-3 py-2 text-xs text-text-secondary\">\n <span title={run.startedAt ? new Date(run.startedAt).toLocaleString() : ''}>\n {relativeTime(run.startedAt)}\n </span>\n </td>\n <td className=\"px-3 py-2 text-xs text-text-secondary font-mono\">\n {formatDuration(run.durationMs)}\n </td>\n <td className=\"px-3 py-2\">\n <SummaryCell run={run} />\n </td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n </section>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"SecurityScanList.js","names":[],"sources":["../../../src/components/security/SecurityScanList.tsx"],"sourcesContent":["/**\n * Recent scan runs list for a vibe, read directly from the agent over\n * the tunnel. No backend GraphQL — see `./agentClient.ts` for the\n * REST surface and `./hooks.ts` for the React Query wrappers.\n *\n * The agent doesn't expose a list-runs endpoint; we derive distinct\n * run ids from the findings cache (see `listRecentScanRuns`). When a\n * vibe has zero findings the list will be empty even if scans have\n * been queued — that's correct: an in-flight queued/running scan\n * doesn't yet have any findings, and the empty state already nudges\n * the user toward kicking off a scan.\n */\nimport { useMemo, type ReactElement } from 'react';\nimport {\n AlertCircle,\n CheckCircle2,\n Clock,\n Loader2,\n MinusCircle,\n ShieldAlert,\n XCircle,\n} from 'lucide-react';\n\nimport { useSecurityRecentScansQuery, type UseSecurityAgentContext } from './hooks';\nimport type { AgentScanConclusion, AgentScanStatus, SecurityScanRun } from './agentClient';\n\ninterface Props extends UseSecurityAgentContext {\n vibeId: string;\n /** Max rows to surface. Defaults to 20. */\n limit?: number;\n}\n\nconst STATUS_BADGE: Record<AgentScanStatus, string> = {\n queued: 'bg-bg-sunken text-text-secondary',\n running: 'bg-status-info-bg-subtle text-status-info-text',\n succeeded: 'bg-status-success-bg-subtle text-status-success-text',\n failed: 'bg-status-error-bg-subtle text-status-error-text',\n errored: 'bg-status-error-bg-subtle text-status-error-text',\n cancelled: 'bg-bg-sunken text-text-tertiary',\n};\n\nconst CONCLUSION_BADGE: Record<AgentScanConclusion, string> = {\n pass: 'bg-status-success-bg-subtle text-status-success-text',\n warn: 'bg-status-warning-bg-subtle text-status-warning-text',\n fail: 'bg-status-error-bg-subtle text-status-error-text',\n skipped: 'bg-bg-sunken text-text-tertiary',\n};\n\nfunction statusIcon(status: AgentScanStatus): ReactElement {\n switch (status) {\n case 'queued':\n return <Clock className=\"size-3.5\" aria-hidden=\"true\" />;\n case 'running':\n return <Loader2 className=\"size-3.5 animate-spin\" aria-hidden=\"true\" />;\n case 'succeeded':\n return <CheckCircle2 className=\"size-3.5\" aria-hidden=\"true\" />;\n case 'failed':\n case 'errored':\n return <XCircle className=\"size-3.5\" aria-hidden=\"true\" />;\n case 'cancelled':\n return <MinusCircle className=\"size-3.5\" aria-hidden=\"true\" />;\n }\n}\n\nfunction relativeTime(epochMs?: number): string {\n if (!epochMs) return '—';\n const diff = Date.now() - epochMs;\n const sec = Math.round(diff / 1000);\n if (sec < 60) return `${sec}s ago`;\n const min = Math.round(sec / 60);\n if (min < 60) return `${min}m ago`;\n const hr = Math.round(min / 60);\n if (hr < 24) return `${hr}h ago`;\n const day = Math.round(hr / 24);\n return `${day}d ago`;\n}\n\nfunction formatDuration(ms?: number): string {\n if (ms === undefined || ms === null) return '—';\n if (ms < 1000) return `${ms}ms`;\n const sec = ms / 1000;\n if (sec < 60) return `${sec.toFixed(1)}s`;\n const min = sec / 60;\n if (min < 60) return `${min.toFixed(1)}m`;\n const hr = min / 60;\n return `${hr.toFixed(1)}h`;\n}\n\n/**\n * Prettify the agent's dotted stage id (e.g. `pull_request.fast` →\n * `pull_request fast`). We don't gate on the canonical 14-stage taxonomy\n * here because providers can — in principle — emit arbitrary stages and\n * we want to surface them rather than hide them.\n */\nfunction formatStage(stage: string): string {\n return stage.replace(/\\./g, ' ');\n}\n\nfunction SummaryCell({ run }: { run: SecurityScanRun }): ReactElement {\n const s = run.summary;\n const total = s.critical + s.high + s.medium + s.low + s.info;\n if (total === 0) {\n return <span className=\"text-xs text-text-tertiary\">clean</span>;\n }\n return (\n <div className=\"flex items-center gap-1 text-xs\">\n {s.critical > 0 && (\n <span\n className=\"inline-flex items-center gap-0.5 rounded px-1.5 py-0.5 bg-status-error-bg-subtle text-status-error-text font-mono\"\n title={`${s.critical} critical`}\n >\n C {s.critical}\n </span>\n )}\n {s.high > 0 && (\n <span\n className=\"inline-flex items-center gap-0.5 rounded px-1.5 py-0.5 bg-status-warning-bg-subtle text-status-warning-text font-mono\"\n title={`${s.high} high`}\n >\n H {s.high}\n </span>\n )}\n {s.medium > 0 && (\n <span\n className=\"inline-flex items-center gap-0.5 rounded px-1.5 py-0.5 bg-status-warning-bg-subtle text-status-warning-text/80 font-mono\"\n title={`${s.medium} medium`}\n >\n M {s.medium}\n </span>\n )}\n {s.low > 0 && (\n <span\n className=\"inline-flex items-center gap-0.5 rounded px-1.5 py-0.5 bg-bg-sunken text-text-secondary font-mono\"\n title={`${s.low} low`}\n >\n L {s.low}\n </span>\n )}\n {s.info > 0 && (\n <span\n className=\"inline-flex items-center gap-0.5 rounded px-1.5 py-0.5 bg-bg-sunken text-text-tertiary font-mono\"\n title={`${s.info} info`}\n >\n I {s.info}\n </span>\n )}\n </div>\n );\n}\n\nfunction SkeletonRow({ rowKey }: { rowKey: number }): ReactElement {\n return (\n <tr key={rowKey} className=\"border-t border-border-subtle\">\n <td className=\"px-3 py-2\">\n <div className=\"h-3 w-20 rounded bg-bg-sunken animate-pulse\" />\n </td>\n <td className=\"px-3 py-2\">\n <div className=\"h-3 w-24 rounded bg-bg-sunken animate-pulse\" />\n </td>\n <td className=\"px-3 py-2\">\n <div className=\"h-3 w-16 rounded bg-bg-sunken animate-pulse\" />\n </td>\n <td className=\"px-3 py-2\">\n <div className=\"h-3 w-16 rounded bg-bg-sunken animate-pulse\" />\n </td>\n <td className=\"px-3 py-2\">\n <div className=\"h-3 w-20 rounded bg-bg-sunken animate-pulse\" />\n </td>\n <td className=\"px-3 py-2\">\n <div className=\"h-3 w-14 rounded bg-bg-sunken animate-pulse\" />\n </td>\n <td className=\"px-3 py-2\">\n <div className=\"h-3 w-28 rounded bg-bg-sunken animate-pulse\" />\n </td>\n </tr>\n );\n}\n\nexport function SecurityScanList({ vibeId, agentId, profile, limit = 20 }: Props): ReactElement {\n const ctx = useMemo(() => ({ agentId, profile }), [agentId, profile]);\n const { data, isLoading, error, isFetching, refetch } = useSecurityRecentScansQuery(\n ctx,\n vibeId,\n limit\n );\n\n // F4 Wave C: agentApiKey + tunnelUrl no longer needed in the browser —\n // the svc proxy owns them. A non-null `agentId` is the only readiness\n // gate (svc rejects unreachable agents with a structured envelope).\n const ready = !!agentId;\n\n if (!ready) {\n return (\n <section\n data-testid=\"security-scan-list\"\n className=\"rounded-lg border border-border-default bg-bg-surface p-4 text-sm text-text-secondary\"\n >\n Connect to a reachable agent to load scan history. Scan runs are read from the agent via the\n svc GraphQL proxy — there's no backend cache.\n </section>\n );\n }\n\n return (\n <section\n data-testid=\"security-scan-list\"\n className=\"rounded-lg border border-border-default bg-bg-surface\"\n >\n <header className=\"flex items-center justify-between gap-3 border-b border-border-default px-4 py-3\">\n <div className=\"min-w-0\">\n <h3 className=\"text-sm font-semibold text-text-primary\">Recent scan runs</h3>\n <p className=\"text-xs text-text-secondary mt-0.5\">\n Read live from the agent. Showing up to {limit} most recently active runs for this vibe.\n </p>\n </div>\n <button\n type=\"button\"\n onClick={() => void refetch()}\n disabled={isFetching}\n className=\"px-2.5 py-1 text-xs rounded-md border border-border-default bg-bg-sunken text-text-primary hover:bg-action-primary-bg-hover disabled:opacity-50\"\n >\n {isFetching ? 'Refreshing…' : 'Refresh'}\n </button>\n </header>\n\n {error && (\n <div className=\"m-4 flex items-start gap-2 rounded-md border border-status-error-border bg-status-error-bg-subtle/10 p-3 text-sm\">\n <AlertCircle className=\"size-4 text-status-error-text mt-0.5 shrink-0\" />\n <div className=\"text-text-primary\">\n <p className=\"font-medium\">Couldn't load scan runs</p>\n <p className=\"mt-0.5 text-xs text-text-secondary\">{error.message}</p>\n </div>\n </div>\n )}\n\n <div className=\"overflow-x-auto\">\n <table className=\"w-full text-sm\">\n <thead className=\"bg-bg-sunken/40 text-text-secondary\">\n <tr>\n <th className=\"px-3 py-2 text-left text-xs font-medium\">Stage</th>\n <th className=\"px-3 py-2 text-left text-xs font-medium\">Provider</th>\n <th className=\"px-3 py-2 text-left text-xs font-medium\">Status</th>\n <th className=\"px-3 py-2 text-left text-xs font-medium\">Conclusion</th>\n <th className=\"px-3 py-2 text-left text-xs font-medium\">Started</th>\n <th className=\"px-3 py-2 text-left text-xs font-medium\">Duration</th>\n <th className=\"px-3 py-2 text-left text-xs font-medium\">Findings</th>\n </tr>\n </thead>\n <tbody>\n {isLoading && data === undefined && (\n <>\n <SkeletonRow rowKey={0} />\n <SkeletonRow rowKey={1} />\n <SkeletonRow rowKey={2} />\n </>\n )}\n {!isLoading && (data?.length ?? 0) === 0 && !error && (\n <tr>\n <td colSpan={7} className=\"px-3 py-8\">\n <div className=\"flex flex-col items-center gap-2 text-center\">\n <ShieldAlert className=\"size-6 text-text-tertiary\" />\n <p className=\"text-sm text-text-primary\">No scans yet</p>\n <p className=\"text-xs text-text-secondary max-w-md\">\n Install a provider above and run a scan to see results here. Findings live in\n the agent's local SQLite cache.\n </p>\n </div>\n </td>\n </tr>\n )}\n {(data ?? []).map((run) => (\n <tr key={run.runId} className=\"border-t border-border-subtle align-middle\">\n <td className=\"px-3 py-2\">\n <span className=\"inline-flex items-center rounded-md bg-bg-sunken px-2 py-0.5 text-xs text-text-primary\">\n {formatStage(run.stage)}\n </span>\n </td>\n <td className=\"px-3 py-2 text-xs text-text-primary\">{run.providerName}</td>\n <td className=\"px-3 py-2\">\n <span\n className={`inline-flex items-center gap-1 rounded-md px-2 py-0.5 text-xs font-medium ${STATUS_BADGE[run.status]}`}\n >\n {statusIcon(run.status)}\n {run.status}\n </span>\n </td>\n <td className=\"px-3 py-2\">\n {run.conclusion ? (\n <span\n className={`inline-flex items-center rounded-md px-2 py-0.5 text-xs font-medium ${CONCLUSION_BADGE[run.conclusion]}`}\n >\n {run.conclusion}\n </span>\n ) : (\n <span className=\"text-xs text-text-tertiary\">—</span>\n )}\n </td>\n <td className=\"px-3 py-2 text-xs text-text-secondary\">\n <span title={run.startedAt ? new Date(run.startedAt).toLocaleString() : ''}>\n {relativeTime(run.startedAt)}\n </span>\n </td>\n <td className=\"px-3 py-2 text-xs text-text-secondary font-mono\">\n {formatDuration(run.durationMs)}\n </td>\n <td className=\"px-3 py-2\">\n <SummaryCell run={run} />\n </td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n </section>\n );\n}\n"],"mappings":"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"}
@@ -32,7 +32,7 @@ var _ = {
32
32
  }
33
33
  },
34
34
  whileTap: { scale: .98 },
35
- className: `bg-bg-surface border rounded-lg p-4 hover:border-border-strong hover:shadow-md transition-all cursor-pointer ${E ? "border-action-primary-bg ring-1 ring-action-primary-bg/30" : "border-border-default"} ${O}`,
35
+ className: `bg-bg-surface border rounded-card shadow-elevation-1 p-5 hover:border-border-strong hover:shadow-elevation-2 transition-all duration-200 ease-[var(--motion-easing-out)] cursor-pointer ${E ? "border-transparent ring-1 ring-[var(--color-focus-ring)] bg-[var(--color-accent-soft)]" : "border-border-subtle"} ${O}`,
36
36
  onClick: () => y?.(v),
37
37
  children: [
38
38
  /* @__PURE__ */ g("div", {
@@ -54,8 +54,8 @@ var _ = {
54
54
  })
55
55
  }),
56
56
  /* @__PURE__ */ h("div", {
57
- className: "flex-shrink-0 size-8 sm:w-10 sm:h-10 rounded-lg bg-bg-sunken flex items-center justify-center",
58
- children: /* @__PURE__ */ h(d, { className: "size-4 sm:w-5 sm:h-5 text-text-secondary" })
57
+ className: "flex-shrink-0 size-9 sm:size-10 rounded-lg bg-[var(--color-accent-soft)] flex items-center justify-center",
58
+ children: /* @__PURE__ */ h(d, { className: "size-4 sm:size-5 text-action-primary-bg" })
59
59
  }),
60
60
  /* @__PURE__ */ g("div", {
61
61
  className: "min-w-0 flex-1",
@@ -63,7 +63,7 @@ var _ = {
63
63
  /* @__PURE__ */ g("div", {
64
64
  className: "flex items-center gap-1.5 sm:gap-2 flex-wrap",
65
65
  children: [/* @__PURE__ */ h("h3", {
66
- className: "font-semibold text-text-primary truncate max-w-[120px] sm:max-w-none text-sm sm:text-base",
66
+ className: "font-semibold text-text-primary truncate max-w-[120px] sm:max-w-none text-sm sm:text-base tracking-tight",
67
67
  children: v.name
68
68
  }), /* @__PURE__ */ h("span", {
69
69
  className: "text-base sm:text-lg flex-shrink-0",
@@ -90,8 +90,12 @@ var _ = {
90
90
  onClick: (e) => {
91
91
  e.stopPropagation(), j(!A);
92
92
  },
93
- className: "p-1 sm:p-1.5 rounded hover:bg-bg-sunken transition-colors",
94
- children: /* @__PURE__ */ h(o, { className: "size-4 text-text-secondary" })
93
+ className: "inline-flex size-8 items-center justify-center rounded-button text-text-secondary hover:bg-bg-sunken hover:text-text-primary transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus",
94
+ "aria-label": k("vibecontrols.sessions.card.actions", "Session actions"),
95
+ children: /* @__PURE__ */ h(o, {
96
+ className: "size-4",
97
+ "aria-hidden": "true"
98
+ })
95
99
  }), /* @__PURE__ */ h(p, { children: A && /* @__PURE__ */ g(m.div, {
96
100
  initial: {
97
101
  opacity: 0,
@@ -109,14 +113,14 @@ var _ = {
109
113
  y: -4
110
114
  },
111
115
  transition: { duration: .15 },
112
- className: "absolute right-0 mt-1 w-40 bg-bg-surface border border-border-default rounded-lg shadow-lg z-10",
116
+ className: "absolute right-0 mt-1.5 w-44 bg-bg-elevated border border-border-subtle rounded-card shadow-elevation-3 z-10 overflow-hidden py-1",
113
117
  children: [
114
118
  P && b && /* @__PURE__ */ g(m.button, {
115
119
  whileTap: { scale: .97 },
116
120
  onClick: (e) => {
117
121
  e.stopPropagation(), b(v), j(!1);
118
122
  },
119
- className: "flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken rounded-t-lg",
123
+ className: "flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken transition-colors",
120
124
  children: [/* @__PURE__ */ h(s, { className: "size-4" }), k("vibecontrols.sessions.card.start", "Start")]
121
125
  }),
122
126
  N && x && /* @__PURE__ */ g(m.button, {
@@ -124,7 +128,7 @@ var _ = {
124
128
  onClick: (e) => {
125
129
  e.stopPropagation(), x(v), j(!1);
126
130
  },
127
- className: "flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken",
131
+ className: "flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken transition-colors",
128
132
  children: [/* @__PURE__ */ h(u, { className: "size-4" }), k("vibecontrols.sessions.card.stop", "Stop")]
129
133
  }),
130
134
  N && S && /* @__PURE__ */ g(m.button, {
@@ -132,7 +136,7 @@ var _ = {
132
136
  onClick: (e) => {
133
137
  e.stopPropagation(), S(v), j(!1);
134
138
  },
135
- className: "flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken",
139
+ className: "flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken transition-colors",
136
140
  children: [/* @__PURE__ */ h(c, { className: "size-4" }), k("vibecontrols.sessions.card.restart", "Restart")]
137
141
  }),
138
142
  N && w && /* @__PURE__ */ g(m.button, {
@@ -140,7 +144,7 @@ var _ = {
140
144
  onClick: (e) => {
141
145
  e.stopPropagation(), w(v), j(!1);
142
146
  },
143
- className: "flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken",
147
+ className: "flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken transition-colors",
144
148
  children: [/* @__PURE__ */ h(d, { className: "size-4" }), k("vibecontrols.sessions.card.openTerminal", "Open Terminal")]
145
149
  }),
146
150
  N && T && /* @__PURE__ */ g(m.button, {
@@ -148,7 +152,7 @@ var _ = {
148
152
  onClick: (e) => {
149
153
  e.stopPropagation(), T(v), j(!1);
150
154
  },
151
- className: "flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken",
155
+ className: "flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken transition-colors",
152
156
  children: [/* @__PURE__ */ h(l, { className: "size-4" }), k("vibecontrols.sessions.card.shareSession", "Share Session")]
153
157
  }),
154
158
  C && /* @__PURE__ */ g(m.button, {
@@ -156,7 +160,7 @@ var _ = {
156
160
  onClick: (e) => {
157
161
  e.stopPropagation(), C(v), j(!1);
158
162
  },
159
- className: "flex items-center gap-2 w-full px-3 py-2 text-sm text-status-error-text hover:bg-bg-sunken rounded-b-lg",
163
+ className: "flex items-center gap-2 w-full px-3 py-2 text-sm text-status-error-text hover:bg-status-error-bg-subtle transition-colors",
160
164
  children: [/* @__PURE__ */ h(f, { className: "size-4" }), k("vibecontrols.sessions.card.terminate", "Terminate")]
161
165
  })
162
166
  ]
@@ -167,7 +171,7 @@ var _ = {
167
171
  (() => {
168
172
  let e = v.metadata || {}, t = typeof e.tmuxSessionName == "string" && e.tmuxSessionName || typeof e.weztermWorkspace == "string" && e.weztermWorkspace || typeof e.zellijSessionName == "string" && e.zellijSessionName || typeof e.externalSessionName == "string" && e.externalSessionName || v.providerSessionName || null;
169
173
  return t ? /* @__PURE__ */ g("div", {
170
- className: "mt-2 text-[10px] font-mono text-text-tertiary",
174
+ className: "mt-3 text-[10px] font-mono text-text-muted",
171
175
  title: `Provider session: ${t}`,
172
176
  "data-testid": "session-card-external-name",
173
177
  children: [/* @__PURE__ */ g("span", {
@@ -180,20 +184,23 @@ var _ = {
180
184
  }) : null;
181
185
  })(),
182
186
  v.command && /* @__PURE__ */ g("div", {
183
- className: "mt-3 text-xs font-mono bg-bg-sunken p-2 rounded truncate text-text-secondary",
187
+ className: "mt-4 text-xs font-mono bg-bg-sunken/60 px-2 py-1.5 rounded truncate text-text-secondary",
184
188
  children: ["$ ", v.command]
185
189
  }),
186
190
  v.lastOutput && /* @__PURE__ */ h("div", {
187
- className: "mt-2 text-xs font-mono bg-bg-sunken text-status-success-text p-2 rounded truncate",
191
+ className: "mt-2 text-xs font-mono bg-bg-sunken/60 text-status-success-text px-2 py-1.5 rounded truncate",
188
192
  children: v.lastOutput
189
193
  }),
190
194
  /* @__PURE__ */ g("div", {
191
- className: "mt-3 flex items-center justify-between text-sm text-text-secondary",
195
+ className: "mt-4 flex items-center justify-between text-sm text-text-secondary",
192
196
  children: [/* @__PURE__ */ g("div", {
193
197
  className: "flex items-center gap-2",
194
- children: [/* @__PURE__ */ h(a, { className: "size-4" }), /* @__PURE__ */ h("span", { children: M(v.updatedAt) })]
198
+ children: [/* @__PURE__ */ h(a, { className: "size-4" }), /* @__PURE__ */ h("span", {
199
+ className: "tabular-nums",
200
+ children: M(v.updatedAt)
201
+ })]
195
202
  }), v.pid && /* @__PURE__ */ g("span", {
196
- className: "text-xs font-mono",
203
+ className: "text-xs font-mono tabular-nums",
197
204
  children: ["PID: ", v.pid]
198
205
  })]
199
206
  })